Select header that will be fully refunded in on-demand batch finality relay (#2729)

* select header that will be fully refunded for submission in on-demand **batch** finality relay

* added the only possible test

* spelling

* nl

* updated comment
This commit is contained in:
Svyatoslav Nikolsky
2023-12-06 15:16:02 +03:00
committed by Bastian Köcher
parent 6d35de23be
commit 6c31cdbe19
15 changed files with 222 additions and 115 deletions
@@ -23,8 +23,9 @@ use bp_header_chain::{
verify_and_optimize_justification, GrandpaEquivocationsFinder, GrandpaJustification,
JustificationVerificationContext,
},
AuthoritySet, ConsensusLogReader, FinalityProof, FindEquivocations, GrandpaConsensusLogReader,
HeaderFinalityInfo, HeaderGrandpaInfo, StoredHeaderGrandpaInfo,
max_expected_submit_finality_proof_arguments_size, AuthoritySet, ConsensusLogReader,
FinalityProof, FindEquivocations, GrandpaConsensusLogReader, HeaderFinalityInfo,
HeaderGrandpaInfo, StoredHeaderGrandpaInfo,
};
use bp_runtime::{BasicOperatingMode, HeaderIdProvider, OperatingMode};
use codec::{Decode, Encode};
@@ -35,9 +36,22 @@ use relay_substrate_client::{
};
use sp_consensus_grandpa::{AuthorityList as GrandpaAuthoritiesSet, GRANDPA_ENGINE_ID};
use sp_core::{storage::StorageKey, Bytes};
use sp_runtime::{scale_info::TypeInfo, traits::Header, ConsensusEngineId};
use sp_runtime::{scale_info::TypeInfo, traits::Header, ConsensusEngineId, SaturatedConversion};
use std::{fmt::Debug, marker::PhantomData};
/// Result of checking maximal expected call size.
pub enum MaxExpectedCallSizeCheck {
/// Size is ok and call will be refunded.
Ok,
/// The call size exceeds the maximal expected and relayer will only get partial refund.
Exceeds {
/// Actual call size.
call_size: u32,
/// Maximal expected call size.
max_call_size: u32,
},
}
/// Finality engine, used by the Substrate chain.
#[async_trait]
pub trait Engine<C: Chain>: Send {
@@ -111,6 +125,14 @@ pub trait Engine<C: Chain>: Send {
proof: &mut Self::FinalityProof,
) -> Result<(), SubstrateError>;
/// Checks whether the given `header` and its finality `proof` fit the maximal expected
/// call size limit. If result is `MaxExpectedCallSizeCheck::Exceeds { .. }`, this
/// submission won't be fully refunded and relayer will spend its own funds on that.
fn check_max_expected_call_size(
header: &C::Header,
proof: &Self::FinalityProof,
) -> MaxExpectedCallSizeCheck;
/// Prepare initialization data for the finality bridge pallet.
async fn prepare_initialization_data(
client: Client<C>,
@@ -219,6 +241,24 @@ impl<C: ChainWithGrandpa> Engine<C> for Grandpa<C> {
})
}
fn check_max_expected_call_size(
header: &C::Header,
proof: &Self::FinalityProof,
) -> MaxExpectedCallSizeCheck {
let is_mandatory = Self::ConsensusLogReader::schedules_authorities_change(header.digest());
let call_size: u32 =
header.encoded_size().saturating_add(proof.encoded_size()).saturated_into();
let max_call_size = max_expected_submit_finality_proof_arguments_size::<C>(
is_mandatory,
proof.commit.precommits.len().saturated_into(),
);
if call_size > max_call_size {
MaxExpectedCallSizeCheck::Exceeds { call_size, max_call_size }
} else {
MaxExpectedCallSizeCheck::Ok
}
}
/// Prepare initialization data for the GRANDPA verifier pallet.
async fn prepare_initialization_data(
source_client: Client<C>,
@@ -16,14 +16,16 @@
//! On-demand Substrate -> Substrate header finality relay.
use crate::finality::SubmitFinalityProofCallBuilder;
use crate::{
finality::SubmitFinalityProofCallBuilder, finality_base::engine::MaxExpectedCallSizeCheck,
};
use async_std::sync::{Arc, Mutex};
use async_trait::async_trait;
use bp_header_chain::ConsensusLogReader;
use bp_runtime::HeaderIdProvider;
use futures::{select, FutureExt};
use num_traits::{One, Zero};
use num_traits::{One, Saturating, Zero};
use sp_runtime::traits::Header;
use finality_relay::{FinalitySyncParams, TargetClient as FinalityTargetClient};
@@ -133,29 +135,61 @@ impl<P: SubstrateFinalitySyncPipeline> OnDemandRelay<P::SourceChain, P::TargetCh
&self,
required_header: BlockNumberOf<P::SourceChain>,
) -> Result<(HeaderIdOf<P::SourceChain>, Vec<CallOf<P::TargetChain>>), SubstrateError> {
// first find proper header (either `required_header`) or its descendant
let finality_source = SubstrateFinalitySource::<P>::new(self.source_client.clone(), None);
let (header, mut proof) = finality_source.prove_block_finality(required_header).await?;
let header_id = header.id();
const MAX_ITERATIONS: u32 = 4;
let mut iterations = 0;
let mut current_required_header = required_header;
loop {
// first find proper header (either `current_required_header`) or its descendant
let finality_source =
SubstrateFinalitySource::<P>::new(self.source_client.clone(), None);
let (header, mut proof) =
finality_source.prove_block_finality(current_required_header).await?;
let header_id = header.id();
// optimize justification before including it into the call
P::FinalityEngine::optimize_proof(&self.target_client, &header, &mut proof).await?;
// optimize justification before including it into the call
P::FinalityEngine::optimize_proof(&self.target_client, &header, &mut proof).await?;
log::debug!(
target: "bridge",
"[{}] Requested to prove {} head {:?}. Selected to prove {} head {:?}",
self.relay_task_name,
P::SourceChain::NAME,
required_header,
P::SourceChain::NAME,
header_id,
);
// now we have the header and its proof, but we want to minimize our losses, so let's
// check if we'll get the full refund for submitting this header
let check_result = P::FinalityEngine::check_max_expected_call_size(&header, &proof);
if let MaxExpectedCallSizeCheck::Exceeds { call_size, max_call_size } = check_result {
iterations += 1;
current_required_header = header_id.number().saturating_add(One::one());
if iterations < MAX_ITERATIONS {
log::debug!(
target: "bridge",
"[{}] Requested to prove {} head {:?}. Selected to prove {} head {:?}. But it is too large: {} vs {}. \
Going to select next header",
self.relay_task_name,
P::SourceChain::NAME,
required_header,
P::SourceChain::NAME,
header_id,
call_size,
max_call_size,
);
// and then craft the submit-proof call
let call =
P::SubmitFinalityProofCallBuilder::build_submit_finality_proof_call(header, proof);
continue;
}
}
Ok((header_id, vec![call]))
log::debug!(
target: "bridge",
"[{}] Requested to prove {} head {:?}. Selected to prove {} head {:?} (after {} iterations)",
self.relay_task_name,
P::SourceChain::NAME,
required_header,
P::SourceChain::NAME,
header_id,
iterations,
);
// and then craft the submit-proof call
let call =
P::SubmitFinalityProofCallBuilder::build_submit_finality_proof_call(header, proof);
return Ok((header_id, vec![call]));
}
}
}