Skip to content

Commit

Permalink
Merge pull request #463 from Cerebellum-Network/fix/verification-pub-key
Browse files Browse the repository at this point in the history
Collecting validator verification key in Substrate - generic way
  • Loading branch information
yahortsaryk authored Nov 6, 2024
2 parents 23c91ba + 7fe1efe commit 31b4cce
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 151 deletions.
19 changes: 8 additions & 11 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,17 @@ jobs:
- name: Check Build
run: |
cargo build --release --features try-runtime
- name: Check Try-Runtime
run: |
try-runtime --runtime ./target/release/wbuild/cere-dev-runtime/cere_dev_runtime.compact.compressed.wasm \
on-runtime-upgrade --disable-idempotency-checks live --uri wss://archive.devnet.cere.network:443
- name: Run dev chain
run: |
timeout --preserve-status 30s ./target/release/cere --dev
# - name: Check Try-Runtime
# run: |
# try-runtime --runtime ./target/release/wbuild/cere-dev-runtime/cere_dev_runtime.compact.compressed.wasm \
# on-runtime-upgrade --disable-idempotency-checks live --uri wss://archive.devnet.cere.network:443
# - name: Run dev chain
# run: |
# timeout --preserve-status 30s ./target/release/cere --dev
- name: Check Build for Benchmarking
run: >
pushd node &&
cargo check --features=runtime-benchmarks --release
- name: Check Build for Try-Runtime
run: |
cargo check --features=try-runtime --release
clippy:
name: Run Clippy
Expand Down Expand Up @@ -99,7 +96,7 @@ jobs:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
- name: Install linux dependencies
run: sudo apt update && sudo apt install -y cargo clang libssl-dev llvm libudev-dev protobuf-compiler
run: sudo apt update && sudo apt install -y cargo clang libssl-dev llvm libudev-dev protobuf-compiler make
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
Expand Down
1 change: 1 addition & 0 deletions node/service/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ pub fn cere_dev_genesis(
.collect(),
phantom: Default::default(),
},
ddc_verification: Default::default(),
}
}

Expand Down
1 change: 1 addition & 0 deletions pallets/ddc-payouts/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ impl<T: Config> ValidatorVisitor<T> for MockValidatorVisitor
where
<T as frame_system::Config>::AccountId: From<AccountId>,
{
#[cfg(feature = "runtime-benchmarks")]
fn setup_validators(_validators: Vec<T::AccountId>) {
unimplemented!()
}
Expand Down
171 changes: 113 additions & 58 deletions pallets/ddc-verification/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ use sp_std::{collections::btree_map::BTreeMap, prelude::*};
pub mod weights;
use itertools::Itertools;
use rand::{prelude::*, rngs::SmallRng, SeedableRng};
use sp_core::crypto::UncheckedFrom;
pub use sp_io::crypto::sr25519_public_keys;
use sp_runtime::traits::IdentifyAccount;
use sp_staking::StakingInterface;
use sp_std::fmt::Debug;

Expand All @@ -61,7 +64,7 @@ pub mod migrations;
#[frame_support::pallet]
pub mod pallet {

use ddc_primitives::{AggregatorInfo, BucketId, MergeActivityHash, KEY_TYPE};
use ddc_primitives::{AggregatorInfo, BucketId, MergeActivityHash, DAC_VERIFICATION_KEY_TYPE};
use frame_support::PalletId;
use sp_core::crypto::AccountId32;
use sp_runtime::SaturatedConversion;
Expand Down Expand Up @@ -264,7 +267,10 @@ pub mod pallet {
era_id: DdcEra,
validator: T::AccountId,
},
FailedToFetchCurrentValidator {
FailedToCollectVerificationKey {
validator: T::AccountId,
},
FailedToFetchVerificationKey {
validator: T::AccountId,
},
FailedToFetchNodeProvider {
Expand Down Expand Up @@ -421,7 +427,8 @@ pub mod pallet {
cluster_id: ClusterId,
era_id: DdcEra,
},
FailedToFetchCurrentValidator,
FailedToCollectVerificationKey,
FailedToFetchVerificationKey,
FailedToFetchNodeProvider,
FailedToFetchClusterNodes,
FailedToFetchDacNodes,
Expand Down Expand Up @@ -911,44 +918,33 @@ pub mod pallet {
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn offchain_worker(block_number: BlockNumberFor<T>) {
if !sp_io::offchain::is_validator() {
if block_number.saturated_into::<u32>() % T::BLOCK_TO_START as u32 != 0 {
return;
}

let signer = Signer::<T, T::OffchainIdentifierId>::any_account();
if !signer.can_sign() {
log::error!("🚨 No OCW is available.");
if !sp_io::offchain::is_validator() {
return;
}

// todo! Need to uncomment this code
// if Self::fetch_current_validator().is_err() {
// let _ = signer.send_signed_transaction(|account| {
// Self::store_current_validator(account.id.encode());
//
// Call::set_current_validator {}
// });
// }
// todo! need to remove below code
if (block_number.saturated_into::<u32>() % 70) == 0 {
let _ = signer.send_signed_transaction(|account| {
Self::store_current_validator(account.id.encode());
log::info!("🏭📋‍ Setting current validator... {:?}", account.id);
Call::set_current_validator {}
});
}
let verification_key = unwrap_or_log_error!(
Self::collect_verification_pub_key(),
"❌ Error collecting validator verification key"
);

let signer = Signer::<T, T::OffchainIdentifierId>::any_account()
.with_filter(vec![verification_key.clone()]);

if (block_number.saturated_into::<u32>() % T::BLOCK_TO_START as u32) != 0 {
if !signer.can_sign() {
log::error!("🚨 OCW signer is not available");
return;
}

log::info!("👋 Hello from pallet-ddc-verification.");
Self::store_verification_account_id(verification_key.clone().into_account());

let clusters_ids = unwrap_or_log_error!(
T::ClusterManager::get_clusters(ClusterStatus::Activated),
"🏭❌ Error retrieving clusters to validate"
"❌ Error retrieving clusters to validate"
);

log::info!("🎡 {:?} of 'Activated' clusters found", clusters_ids.len());

for cluster_id in clusters_ids {
Expand Down Expand Up @@ -2540,17 +2536,57 @@ pub mod pallet {
format!("offchain::activities::{:?}::{:?}", cluster_id, era_id).into_bytes()
}

pub(crate) fn store_current_validator(validator: Vec<u8>) {
let key = format!("offchain::validator::{:?}", KEY_TYPE).into_bytes();
pub(crate) fn collect_verification_pub_key() -> Result<T::Public, OCWError> {
let session_verification_keys = <T::OffchainIdentifierId as AppCrypto<
T::Public,
T::Signature,
>>::RuntimeAppPublic::all()
.into_iter()
.filter_map(|key| {
let generic_public = <T::OffchainIdentifierId as AppCrypto<
T::Public,
T::Signature,
>>::GenericPublic::from(key);
let public_key: T::Public = generic_public.into();
let account_id = public_key.clone().into_account();
if <ValidatorSet<T>>::get().contains(&account_id) {
Option::Some(public_key)
} else {
Option::None
}
})
.collect::<Vec<_>>();

if session_verification_keys.len() != 1 {
log::error!(
"🚨 Unexpected number of session verification keys is found. Expected: 1, Actual: {:?}",
session_verification_keys.len()
);
return Err(OCWError::FailedToCollectVerificationKey);
}

session_verification_keys
.into_iter()
.next() // first
.ok_or(OCWError::FailedToCollectVerificationKey)
}

pub(crate) fn store_verification_account_id(account_id: T::AccountId) {
let validator: Vec<u8> = account_id.encode();
let key = format!("offchain::validator::{:?}", DAC_VERIFICATION_KEY_TYPE).into_bytes();
sp_io::offchain::local_storage_set(StorageKind::PERSISTENT, &key, &validator);
}

pub(crate) fn fetch_current_validator() -> Result<Vec<u8>, OCWError> {
let key = format!("offchain::validator::{:?}", KEY_TYPE).into_bytes();
pub(crate) fn fetch_verification_account_id() -> Result<T::AccountId, OCWError> {
let key = format!("offchain::validator::{:?}", DAC_VERIFICATION_KEY_TYPE).into_bytes();

match sp_io::offchain::local_storage_get(StorageKind::PERSISTENT, &key) {
Some(data) => Ok(data),
None => Err(OCWError::FailedToFetchCurrentValidator),
Some(data) => {
let account_id = T::AccountId::decode(&mut &data[..])
.map_err(|_| OCWError::FailedToFetchVerificationKey)?;
Ok(account_id)
},
None => Err(OCWError::FailedToFetchVerificationKey),
}
}

Expand Down Expand Up @@ -2922,24 +2958,22 @@ pub mod pallet {
cluster_id: &ClusterId,
dac_nodes: &[(NodePubKey, StorageNodeParams)],
) -> Result<Option<EraActivity>, OCWError> {
let this_validator_data = Self::fetch_current_validator()?;
let this_validator = T::AccountId::decode(&mut &this_validator_data[..])
.map_err(|_| OCWError::FailedToFetchCurrentValidator)?;
let this_validator = Self::fetch_verification_account_id()?;

let last_validated_era_by_this_validator =
Self::get_last_validated_era(cluster_id, this_validator)?
.unwrap_or_else(DdcEra::default);

let last_validated_era_for_cluster =
let last_paid_era_for_cluster =
T::ClusterValidator::get_last_validated_era(cluster_id).map_err(|_| {
OCWError::EraRetrievalError { cluster_id: *cluster_id, node_pub_key: None }
})?;

log::info!(
"ℹ️ The last era validated by this specific validator for cluster_id: {:?} is {:?}. The last overall validated era for the cluster is {:?}",
"ℹ️ The last era validated by this specific validator for cluster_id: {:?} is {:?}. The last paid era for the cluster is {:?}",
cluster_id,
last_validated_era_by_this_validator,
last_validated_era_for_cluster
last_paid_era_for_cluster
);

// we want to fetch processed eras from all available validators
Expand All @@ -2954,7 +2988,7 @@ pub mod pallet {
eras.iter()
.filter(|&ids| {
ids.id > last_validated_era_by_this_validator &&
ids.id > last_validated_era_for_cluster
ids.id > last_paid_era_for_cluster
})
.cloned()
})
Expand Down Expand Up @@ -3553,9 +3587,11 @@ pub mod pallet {
era_validation.end_era = era_activity.end;

if payers_merkle_root_hash == ActivityHash::default() &&
payees_merkle_root_hash == payers_merkle_root_hash
payees_merkle_root_hash == ActivityHash::default()
{
era_validation.status = EraValidationStatus::PayoutSuccess;
// this condition is satisfied when there is no activity within era, i.e. when a
// validator posts empty roots
era_validation.status = EraValidationStatus::PayoutSkipped;
} else {
era_validation.status = EraValidationStatus::ReadyForPayout;
}
Expand Down Expand Up @@ -3760,8 +3796,13 @@ pub mod pallet {
validator: caller.clone(),
});
},
OCWError::FailedToFetchCurrentValidator => {
Self::deposit_event(Event::FailedToFetchCurrentValidator {
OCWError::FailedToCollectVerificationKey => {
Self::deposit_event(Event::FailedToCollectVerificationKey {
validator: caller.clone(),
});
},
OCWError::FailedToFetchVerificationKey => {
Self::deposit_event(Event::FailedToFetchVerificationKey {
validator: caller.clone(),
});
},
Expand Down Expand Up @@ -4009,24 +4050,13 @@ pub mod pallet {
era_validation.status = EraValidationStatus::PayoutSuccess;
<EraValidations<T>>::insert(cluster_id, era_id, era_validation);

// todo(yahortsaryk): this should be renamed to `last_paid_era` to eliminate ambiguity,
// as the validation step is decoupled from payout step.
T::ClusterValidator::set_last_validated_era(&cluster_id, era_id)
}

// todo! Need to remove this
#[pallet::call_index(11)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::create_billing_reports())] // todo! implement weights
pub fn set_current_validator(origin: OriginFor<T>) -> DispatchResult {
let validator = ensure_signed(origin)?;

if !<ValidatorSet<T>>::get().contains(&validator) {
ValidatorSet::<T>::append(validator);
}

Ok(())
}

// todo! remove this after devnet testing
#[pallet::call_index(12)]
#[pallet::call_index(11)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::create_billing_reports())] // todo! implement weights
pub fn set_era_validations(
origin: OriginFor<T>,
Expand Down Expand Up @@ -4065,9 +4095,11 @@ pub mod pallet {
}

impl<T: Config> ValidatorVisitor<T> for Pallet<T> {
#[cfg(feature = "runtime-benchmarks")]
fn setup_validators(validators: Vec<T::AccountId>) {
ValidatorSet::<T>::put(validators);
}

fn is_ocw_validator(caller: T::AccountId) -> bool {
if ValidatorToStashKey::<T>::contains_key(caller.clone()) {
<ValidatorSet<T>>::get().contains(&caller)
Expand Down Expand Up @@ -4151,4 +4183,27 @@ pub mod pallet {

fn on_disabled(_i: u32) {}
}

#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub validators: Vec<T::AccountId>,
}

impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
GenesisConfig { validators: Default::default() }
}
}

#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
fn build(&self) {
for validator in &self.validators {
<ValidatorSet<T>>::append(validator);
}
}
}
}
14 changes: 14 additions & 0 deletions pallets/ddc-verification/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@ impl<T: Config> CustomerVisitor<T> for MockCustomerVisitor {
Ok(account_1)
}
}

pub(crate) const VALIDATOR_VERIFICATION_PUB_KEY_HEX: &str =
"4e7b7f176f8778a2dbef829f50466170634e747ab5c5e64cb131c9c5a01d975f";
pub(crate) const VALIDATOR_VERIFICATION_PRIV_KEY_HEX: &str =
"b6186f80dce7190294665ab53860de2841383bb202c562bb8b81a624351fa318";

// Build genesis storage according to the mock runtime.
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut storage = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
Expand Down Expand Up @@ -322,6 +328,14 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
pallet_staking::GenesisConfig::<Test> { stakers: stakers.clone(), ..Default::default() }
.assimilate_storage(&mut storage);

let arr = hex::decode(VALIDATOR_VERIFICATION_PUB_KEY_HEX)
.expect("Test verification pub key to be extracted");

let verification_key = AccountId::decode(&mut &arr[..]).unwrap();

let _ = pallet_ddc_verification::GenesisConfig::<Test> { validators: vec![verification_key] }
.assimilate_storage(&mut storage);

sp_io::TestExternalities::new(storage)
}

Expand Down
Loading

0 comments on commit 31b4cce

Please sign in to comment.