Skip to content

Commit

Permalink
feat: add initiated_at block number for egresses (#4046)
Browse files Browse the repository at this point in the history
* feat: add initiated_at block number for egresses

* chore: add migration for chain height addition

* chore: add migration hooks
  • Loading branch information
kylezs authored and dandanlen committed Sep 29, 2023
1 parent a3f4c81 commit d8cdba8
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 15 deletions.
21 changes: 14 additions & 7 deletions state-chain/pallets/cf-broadcast/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ fn insert_transaction_broadcast_attempt<T: pallet::Config<I>, I: 'static>(
);
}

const INITIATED_AT: u32 = 100;

pub type AggKeyFor<T, I> = <<<T as pallet::Config<I>>::TargetChain as cf_chains::Chain>::ChainCrypto as ChainCrypto>::AggKey;

// Generates a new signature ready call.
fn generate_on_signature_ready_call<T: pallet::Config<I>, I>() -> pallet::Call<T, I> {
let threshold_request_id = 1;
Expand All @@ -46,6 +50,7 @@ fn generate_on_signature_ready_call<T: pallet::Config<I>, I>() -> pallet::Call<T
threshold_signature_payload: PayloadFor::<T, I>::benchmark_value(),
api_call: Box::new(ApiCallFor::<T, I>::benchmark_value()),
broadcast_id: 1,
initiated_at: INITIATED_AT.into(),
}
}

Expand All @@ -61,7 +66,7 @@ benchmarks_instance_pallet! {
Timeouts::<T, I>::append(timeout_block, broadcast_attempt_id);
ThresholdSignatureData::<T, I>::insert(i, (ApiCallFor::<T, I>::benchmark_value(), ThresholdSignatureFor::<T, I>::benchmark_value()))
}
let valid_key = <<<T as Config<I>>::TargetChain as Chain>::ChainCrypto as ChainCrypto>::AggKey::benchmark_value();
let valid_key = AggKeyFor::<T, I>::benchmark_value();
T::KeyProvider::set_key(valid_key);
} : {
Pallet::<T, I>::on_initialize(timeout_block);
Expand All @@ -79,7 +84,7 @@ benchmarks_instance_pallet! {
insert_transaction_broadcast_attempt::<T, I>(caller.clone().into(), broadcast_attempt_id);
generate_on_signature_ready_call::<T, I>().dispatch_bypass_filter(T::EnsureThresholdSigned::try_successful_origin().unwrap())?;
let expiry_block = frame_system::Pallet::<T>::block_number() + T::BroadcastTimeout::get();
let valid_key = <<<T as Config<I>>::TargetChain as Chain>::ChainCrypto as ChainCrypto>::AggKey::benchmark_value();
let valid_key = AggKeyFor::<T, I>::benchmark_value();
T::KeyProvider::set_key(valid_key);
}: _(RawOrigin::Signed(caller), broadcast_attempt_id)
verify {
Expand All @@ -94,7 +99,7 @@ benchmarks_instance_pallet! {
};
insert_transaction_broadcast_attempt::<T, I>(whitelisted_caller(), broadcast_attempt_id);
let call = generate_on_signature_ready_call::<T, I>();
let valid_key = <<<T as Config<I>>::TargetChain as Chain>::ChainCrypto as ChainCrypto>::AggKey::benchmark_value();
let valid_key = AggKeyFor::<T, I>::benchmark_value();
T::KeyProvider::set_key(valid_key);
} : { call.dispatch_bypass_filter(T::EnsureThresholdSigned::try_successful_origin().unwrap())? }
verify {
Expand All @@ -110,10 +115,11 @@ benchmarks_instance_pallet! {
BenchmarkValue::benchmark_value(),
signed_api_call,
BenchmarkValue::benchmark_value(),
1
1,
INITIATED_AT.into(),
);

T::KeyProvider::set_key(<<<T as Config<I>>::TargetChain as Chain>::ChainCrypto as ChainCrypto>::AggKey::benchmark_value());
T::KeyProvider::set_key(AggKeyFor::<T, I>::benchmark_value());
let transaction_payload = TransactionFor::<T, I>::benchmark_value();

} : {
Expand All @@ -130,7 +136,8 @@ benchmarks_instance_pallet! {
transaction_succeeded {
let caller: T::AccountId = whitelisted_caller();
let signer_id = SignerIdFor::<T, I>::benchmark_value();
TransactionOutIdToBroadcastId::<T, I>::insert(TransactionOutIdFor::<T, I>::benchmark_value(), 1);
let initiated_at: ChainBlockNumberFor<T, I> = INITIATED_AT.into();
TransactionOutIdToBroadcastId::<T, I>::insert(TransactionOutIdFor::<T, I>::benchmark_value(), (1, initiated_at));

let broadcast_attempt_id = BroadcastAttemptId {
broadcast_id: 1,
Expand All @@ -142,7 +149,7 @@ benchmarks_instance_pallet! {
signer_id,
tx_fee: TransactionFeeFor::<T, I>::benchmark_value(),
};
let valid_key = <<<T as Config<I>>::TargetChain as Chain>::ChainCrypto as ChainCrypto>::AggKey::benchmark_value();
let valid_key = AggKeyFor::<T, I>::benchmark_value();
T::KeyProvider::set_key(valid_key);
} : { call.dispatch_bypass_filter(T::EnsureWitnessedAtCurrentEpoch::try_successful_origin().unwrap())? }
verify {
Expand Down
55 changes: 48 additions & 7 deletions state-chain/pallets/cf-broadcast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ mod benchmarking;
mod mock;
mod tests;

pub mod migrations;
pub mod weights;
use cf_primitives::{BroadcastId, ThresholdSignatureRequestId};
use cf_traits::impl_pallet_safe_mode;
use cf_traits::{impl_pallet_safe_mode, GetBlockHeight};
pub use weights::WeightInfo;

impl_pallet_safe_mode!(PalletSafeMode; retry_enabled);
Expand All @@ -23,7 +24,7 @@ use frame_support::{
dispatch::DispatchResultWithPostInfo,
pallet_prelude::DispatchResult,
sp_runtime::traits::Saturating,
traits::{Get, UnfilteredDispatchable},
traits::{Get, OnRuntimeUpgrade, StorageVersion, UnfilteredDispatchable},
Twox64Concat,
};

Expand Down Expand Up @@ -66,6 +67,8 @@ pub enum PalletOffence {
FailedToBroadcastTransaction,
}

pub const PALLET_VERSION: StorageVersion = StorageVersion::new(1);

#[frame_support::pallet]
pub mod pallet {
use super::*;
Expand Down Expand Up @@ -179,6 +182,9 @@ pub mod pallet {

type BroadcastReadyProvider: OnBroadcastReady<Self::TargetChain, ApiCall = Self::ApiCall>;

/// Get the latest block height of the target chain via Chain Tracking.
type ChainTracking: GetBlockHeight<Self::TargetChain>;

/// The timeout duration for the broadcast, measured in number of blocks.
#[pallet::constant]
type BroadcastTimeout: Get<BlockNumberFor<Self>>;
Expand All @@ -202,6 +208,7 @@ pub mod pallet {
pub struct Origin<T: Config<I>, I: 'static = ()>(pub(super) PhantomData<(T, I)>);

#[pallet::pallet]
#[pallet::storage_version(PALLET_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);

Expand Down Expand Up @@ -237,8 +244,13 @@ pub mod pallet {

/// Lookup table between TransactionOutId -> Broadcast.
#[pallet::storage]
pub type TransactionOutIdToBroadcastId<T: Config<I>, I: 'static = ()> =
StorageMap<_, Twox64Concat, TransactionOutIdFor<T, I>, BroadcastId, OptionQuery>;
pub type TransactionOutIdToBroadcastId<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Twox64Concat,
TransactionOutIdFor<T, I>,
(BroadcastId, ChainBlockNumberFor<T, I>),
OptionQuery,
>;

/// The list of failed broadcasts pending retry.
#[pallet::storage]
Expand Down Expand Up @@ -368,6 +380,20 @@ pub mod pallet {
Weight::zero()
}
}

fn on_runtime_upgrade() -> Weight {
migrations::PalletMigration::<T, I>::on_runtime_upgrade()
}

#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<sp_std::vec::Vec<u8>, DispatchError> {
migrations::PalletMigration::<T, I>::pre_upgrade()
}

#[cfg(feature = "try-runtime")]
fn post_upgrade(state: sp_std::vec::Vec<u8>) -> Result<(), DispatchError> {
migrations::PalletMigration::<T, I>::post_upgrade(state)
}
}

#[pallet::call]
Expand Down Expand Up @@ -452,6 +478,7 @@ pub mod pallet {
threshold_signature_payload: PayloadFor<T, I>,
api_call: Box<<T as Config<I>>::ApiCall>,
broadcast_id: BroadcastId,
initiated_at: ChainBlockNumberFor<T, I>,
) -> DispatchResultWithPostInfo {
let _ = T::EnsureThresholdSigned::ensure_origin(origin)?;

Expand All @@ -474,6 +501,7 @@ pub mod pallet {
signed_api_call,
threshold_signature_payload,
broadcast_id,
initiated_at,
);
Ok(().into())
}
Expand Down Expand Up @@ -502,8 +530,9 @@ pub mod pallet {
) -> DispatchResultWithPostInfo {
T::EnsureWitnessed::ensure_origin(origin.clone())?;

let broadcast_id = TransactionOutIdToBroadcastId::<T, I>::take(&tx_out_id)
.ok_or(Error::<T, I>::InvalidPayload)?;
let (broadcast_id, _initiated_at) =
TransactionOutIdToBroadcastId::<T, I>::take(&tx_out_id)
.ok_or(Error::<T, I>::InvalidPayload)?;

let to_refund = AwaitingBroadcast::<T, I>::get(BroadcastAttemptId {
broadcast_id,
Expand Down Expand Up @@ -616,6 +645,13 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
if let Some(callback) = maybe_callback {
RequestCallbacks::<T, I>::insert(broadcast_id, callback);
}

// We must set this here because after the threshold signature is requested, it's
// possible that an authority submits the transaction themselves, not going through the
// standard path. This protects against that, to ensure we always set the earliest possible
// block number we could have broadcast at, so that we can ensure we witness it.
let initiated_at = T::ChainTracking::get_block_height();

let threshold_signature_payload = api_call.threshold_signature_payload();
let signature_request_id = T::ThresholdSigner::request_signature_with_callback(
threshold_signature_payload.clone(),
Expand All @@ -625,6 +661,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
threshold_signature_payload,
api_call: Box::new(api_call),
broadcast_id,
initiated_at,
}
.into()
},
Expand All @@ -643,12 +680,16 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
api_call: <T as Config<I>>::ApiCall,
threshold_signature_payload: <<T::TargetChain as Chain>::ChainCrypto as ChainCrypto>::Payload,
broadcast_id: BroadcastId,
initiated_at: ChainBlockNumberFor<T, I>,
) -> BroadcastAttemptId {
let transaction_out_id = api_call.transaction_out_id();

T::BroadcastReadyProvider::on_broadcast_ready(&api_call);

TransactionOutIdToBroadcastId::<T, I>::insert(&transaction_out_id, broadcast_id);
TransactionOutIdToBroadcastId::<T, I>::insert(
&transaction_out_id,
(broadcast_id, initiated_at),
);

ThresholdSignatureData::<T, I>::insert(broadcast_id, (api_call, signature));

Expand Down
6 changes: 6 additions & 0 deletions state-chain/pallets/cf-broadcast/src/migrations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pub mod add_initiated_at;

use cf_runtime_upgrade_utilities::VersionedMigration;

pub type PalletMigration<T, I> =
(VersionedMigration<crate::Pallet<T, I>, add_initiated_at::Migration<T, I>, 0, 1>,);
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use crate::*;
#[cfg(feature = "try-runtime")]
use frame_support::dispatch::DispatchError;
use frame_support::{traits::OnRuntimeUpgrade, weights::Weight};
use sp_std::marker::PhantomData;

mod old {
use frame_support::pallet_prelude::OptionQuery;

use super::*;

#[frame_support::storage_alias]
pub type TransactionOutIdToBroadcastId<T: Config<I>, I: 'static> =
StorageMap<Pallet<T, I>, Twox64Concat, TransactionOutIdFor<T, I>, BroadcastId, OptionQuery>;
}

pub struct Migration<T: Config<I>, I: 'static>(PhantomData<(T, I)>);

impl<T: Config<I>, I: 'static> OnRuntimeUpgrade for Migration<T, I> {
fn on_runtime_upgrade() -> frame_support::weights::Weight {
let chain_height = T::ChainTracking::get_block_height();

TransactionOutIdToBroadcastId::<T, I>::translate::<BroadcastId, _>(|_id, old| {
Some((old, chain_height))
});

Weight::zero()
}

#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, DispatchError> {
use frame_support::ensure;

let chain_height = T::ChainTracking::get_block_height();
// If it's at 0 something went wrong with the initialisation. Also since initiated_at is the
// last thing being decoded, this acts as a check that the rest of the decoding worked.
ensure!(chain_height > 0u32.into(), "chain_height is 0");
Ok(chain_height.encode())
}

#[cfg(feature = "try-runtime")]
fn post_upgrade(state: Vec<u8>) -> Result<(), DispatchError> {
use frame_support::ensure;

let pre_upgrade_height = ChainBlockNumberFor::<T, I>::decode(&mut &state[..])
.map_err(|_| "Failed to decode pre-upgrade state.")?;

for (_out_id, (_b_id, initiated_at)) in TransactionOutIdToBroadcastId::<T, I>::iter() {
ensure!(initiated_at >= pre_upgrade_height, "initiated_at is 0");
}
Ok(())
}
}
6 changes: 5 additions & 1 deletion state-chain/pallets/cf-broadcast/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ use cf_chains::{
};
use cf_traits::{
impl_mock_chainflip, impl_mock_runtime_safe_mode,
mocks::{signer_nomination::MockNominator, threshold_signer::MockThresholdSigner},
mocks::{
block_height_provider::BlockHeightProvider, signer_nomination::MockNominator,
threshold_signer::MockThresholdSigner,
},
AccountRoleRegistry, EpochKey, KeyState, OnBroadcastReady,
};
use codec::{Decode, Encode};
Expand Down Expand Up @@ -152,6 +155,7 @@ impl pallet_cf_broadcast::Config<Instance1> for Test {
type SafeMode = MockRuntimeSafeMode;
type BroadcastReadyProvider = MockBroadcastReadyProvider;
type SafeModeBlockMargin = ConstU64<10>;
type ChainTracking = BlockHeightProvider<MockEthereum>;
}

impl_mock_chainflip!(Test);
Expand Down
1 change: 1 addition & 0 deletions state-chain/pallets/cf-broadcast/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ fn start_mock_broadcast_tx_out_id(
MockApiCall { tx_out_id, payload: Default::default(), sig: Default::default() },
Default::default(),
1,
100u64,
)
}

Expand Down
3 changes: 3 additions & 0 deletions state-chain/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,7 @@ impl pallet_cf_broadcast::Config<EthereumInstance> for Runtime {
type SafeMode = RuntimeSafeMode;
type SafeModeBlockMargin = ConstU32<10>;
type KeyProvider = EthereumVault;
type ChainTracking = EthereumChainTracking;
}

impl pallet_cf_broadcast::Config<PolkadotInstance> for Runtime {
Expand All @@ -704,6 +705,7 @@ impl pallet_cf_broadcast::Config<PolkadotInstance> for Runtime {
type SafeMode = RuntimeSafeMode;
type SafeModeBlockMargin = ConstU32<10>;
type KeyProvider = PolkadotVault;
type ChainTracking = PolkadotChainTracking;
}

impl pallet_cf_broadcast::Config<BitcoinInstance> for Runtime {
Expand All @@ -726,6 +728,7 @@ impl pallet_cf_broadcast::Config<BitcoinInstance> for Runtime {
type SafeMode = RuntimeSafeMode;
type SafeModeBlockMargin = ConstU32<10>;
type KeyProvider = BitcoinVault;
type ChainTracking = BitcoinChainTracking;
}

impl pallet_cf_chain_tracking::Config<EthereumInstance> for Runtime {
Expand Down

0 comments on commit d8cdba8

Please sign in to comment.