Skip to content

Commit

Permalink
Merge pull request #1656 from subspace/bundle-producer-election-part-1
Browse files Browse the repository at this point in the history
Bundle producer election (part 1)
  • Loading branch information
liuchengxu authored Jul 12, 2023
2 parents 291c654 + 7df3658 commit a98573e
Show file tree
Hide file tree
Showing 19 changed files with 372 additions and 298 deletions.
95 changes: 79 additions & 16 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@ use frame_support::traits::Get;
use frame_system::offchain::SubmitTransaction;
pub use pallet::*;
use sp_core::H256;
use sp_domains::bundle_producer_election::BundleProducerElectionParams;
use sp_domains::fraud_proof::FraudProof;
use sp_domains::{DomainId, OpaqueBundle, OperatorId};
use sp_domains::{DomainId, OpaqueBundle, OperatorId, OperatorPublicKey};
use sp_runtime::traits::{BlockNumberProvider, CheckedSub, One, Zero};
use sp_runtime::transaction_validity::TransactionValidityError;
use sp_runtime::RuntimeAppPublic;
use sp_std::vec::Vec;
use subspace_core_primitives::U256;

Expand All @@ -56,6 +58,9 @@ pub trait FreezeIdentifier<T: Config> {

#[frame_support::pallet]
mod pallet {
// TODO: a complaint on `submit_bundle` call, revisit once new v2 features are complete.
#![allow(clippy::large_enum_variant)]

use crate::domain_registry::{
do_instantiate_domain, DomainConfig, DomainObject, Error as DomainRegistryError,
};
Expand All @@ -80,15 +85,14 @@ mod pallet {
use sp_core::H256;
use sp_domains::fraud_proof::FraudProof;
use sp_domains::transaction::InvalidTransactionCode;
use sp_domains::{
DomainId, GenesisDomain, OpaqueBundle, OperatorId, OperatorPublicKey, RuntimeId,
RuntimeType,
};
use sp_domains::{DomainId, GenesisDomain, OpaqueBundle, OperatorId, RuntimeId, RuntimeType};
use sp_runtime::traits::{
AtLeast32BitUnsigned, BlockNumberProvider, Bounded, CheckEqual, MaybeDisplay, SimpleBitOps,
Zero,
};
use sp_runtime::SaturatedConversion;
use sp_std::fmt::Debug;
use sp_std::vec;
use sp_std::vec::Vec;
use subspace_core_primitives::U256;

Expand Down Expand Up @@ -292,8 +296,8 @@ mod pallet {

#[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
pub enum BundleError {
/// The signer of bundle is unexpected.
UnexpectedSigner,
/// Can not find the operator for given operator id.
InvalidOperatorId,
/// Invalid bundle signature.
BadSignature,
/// Invalid vrf proof.
Expand Down Expand Up @@ -376,7 +380,7 @@ mod pallet {
BundleStored {
domain_id: DomainId,
bundle_hash: H256,
bundle_author: OperatorPublicKey,
bundle_author: OperatorId,
},
DomainRuntimeCreated {
runtime_id: RuntimeId,
Expand Down Expand Up @@ -486,7 +490,7 @@ mod pallet {
Self::deposit_event(Event::BundleStored {
domain_id,
bundle_hash,
bundle_author: opaque_bundle.into_operator_public_key(),
bundle_author: opaque_bundle.operator_id(),
});

Ok(())
Expand Down Expand Up @@ -709,12 +713,44 @@ mod pallet {

// Instantiate the genesis domain
let domain_config = DomainConfig::from_genesis::<T>(genesis_domain, runtime_id);
do_instantiate_domain::<T>(
domain_config,
genesis_domain.owner_account_id.clone(),
Zero::zero(),
let domain_owner = genesis_domain.owner_account_id.clone();
let domain_id =
do_instantiate_domain::<T>(domain_config, domain_owner.clone(), Zero::zero())
.expect("Genesis domain instantiation must always succeed");

// Register domain_owner as the genesis operator.
let operator_config = OperatorConfig {
signing_key: genesis_domain.signing_key.clone(),
minimum_nominator_stake: genesis_domain
.minimum_nominator_stake
.saturated_into(),
nomination_tax: genesis_domain.nomination_tax,
};
let operator_stake = T::MinOperatorStake::get();
let operator_id = do_register_operator::<T>(
domain_owner,
domain_id,
operator_stake,
operator_config,
)
.expect("Genesis domain instantiation must always succeed");
.expect("Genesis operator registration must succeed");

// TODO: Enact the epoch transition logic properly.
Operators::<T>::mutate(operator_id, |maybe_operator| {
let operator = maybe_operator
.as_mut()
.expect("Genesis operator must exist");
operator.current_total_stake = operator_stake;
});
DomainStakingSummary::<T>::insert(
domain_id,
StakingSummary {
current_epoch_index: 0,
current_total_stake: operator_stake,
current_operators: vec![operator_id],
next_operators: vec![],
},
);
}
}
}
Expand Down Expand Up @@ -816,6 +852,27 @@ impl<T: Config> Pallet<T> {
.unwrap_or_else(Self::initial_tx_range)
}

pub fn bundle_producer_election_params(
domain_id: DomainId,
) -> Option<BundleProducerElectionParams<BalanceOf<T>>> {
match (
DomainRegistry::<T>::get(domain_id),
DomainStakingSummary::<T>::get(domain_id),
) {
(Some(domain_object), Some(stake_summary)) => Some(BundleProducerElectionParams {
current_operators: stake_summary.current_operators,
total_domain_stake: stake_summary.current_total_stake,
bundle_slot_probability: domain_object.domain_config.bundle_slot_probability,
}),
_ => None,
}
}

pub fn operator(operator_id: OperatorId) -> Option<(OperatorPublicKey, BalanceOf<T>)> {
Operators::<T>::get(operator_id)
.map(|operator| (operator.signing_key, operator.current_total_stake))
}

fn pre_dispatch_submit_bundle(
_opaque_bundle: &OpaqueBundle<T::BlockNumber, T::Hash, T::DomainNumber, T::DomainHash>,
) -> Result<(), TransactionValidityError> {
Expand All @@ -830,7 +887,11 @@ impl<T: Config> Pallet<T> {
extrinsics: _,
}: &OpaqueBundle<T::BlockNumber, T::Hash, T::DomainNumber, T::DomainHash>,
) -> Result<(), BundleError> {
if !sealed_header.verify_signature() {
let signing_key = Operators::<T>::get(sealed_header.header.proof_of_election.operator_id)
.map(|operator| operator.signing_key)
.ok_or(BundleError::InvalidOperatorId)?;

if !signing_key.verify(&sealed_header.pre_hash(), &sealed_header.signature) {
return Err(BundleError::BadSignature);
}

Expand Down Expand Up @@ -868,6 +929,8 @@ impl<T: Config> Pallet<T> {

// TODO: Implement bundle validation.

// TODO: Verify ProofOfElection

Ok(())
}

Expand Down Expand Up @@ -917,7 +980,7 @@ where
pub fn submit_bundle_unsigned(
opaque_bundle: OpaqueBundle<T::BlockNumber, T::Hash, T::DomainNumber, T::DomainHash>,
) {
let slot = opaque_bundle.sealed_header.header.slot_number;
let slot = opaque_bundle.sealed_header.slot_number();
let extrincis_count = opaque_bundle.extrinsics.len();

let call = Call::submit_bundle { opaque_bundle };
Expand Down
10 changes: 5 additions & 5 deletions crates/pallet-domains/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ use scale_info::TypeInfo;
use sp_core::crypto::Pair;
use sp_core::{Get, H256, U256};
use sp_domains::{
create_dummy_bundle_with_receipts_generic, BundleHeader, BundleSolution, DomainId,
DomainsFreezeIdentifier, ExecutionReceipt, OpaqueBundle, OperatorId, OperatorPair,
SealedBundleHeader,
create_dummy_bundle_with_receipts_generic, BundleHeader, DomainId, DomainsFreezeIdentifier,
ExecutionReceipt, OpaqueBundle, OperatorId, OperatorPair, ProofOfElection, SealedBundleHeader,
};
use sp_runtime::testing::Header;
use sp_runtime::traits::{BlakeTwo256, IdentityLookup};
Expand Down Expand Up @@ -202,9 +201,8 @@ fn create_dummy_bundle(
let header = BundleHeader {
consensus_block_number,
consensus_block_hash,
slot_number: 0u64,
extrinsics_root: Default::default(),
bundle_solution: BundleSolution::dummy(domain_id, pair.public()),
proof_of_election: ProofOfElection::dummy(domain_id, 0u64),
};

let signature = pair.sign(header.hash().as_ref());
Expand All @@ -231,7 +229,9 @@ fn create_dummy_bundle_with_receipts(
)
}

// TODO: Unblock once bundle producer election v2 is finished.
#[test]
#[ignore]
fn test_stale_bundle_should_be_rejected() {
// Small macro in order to be more readable.
//
Expand Down
99 changes: 0 additions & 99 deletions crates/sp-domains/src/bundle_election.rs

This file was deleted.

83 changes: 83 additions & 0 deletions crates/sp-domains/src/bundle_producer_election.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use crate::{DomainId, OperatorId, OperatorPublicKey, StakeWeight};
use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_core::crypto::{VrfPublic, Wraps};
use sp_core::sr25519::vrf::{VrfOutput, VrfSignature, VrfTranscript};
use sp_std::vec::Vec;
use subspace_core_primitives::Blake2b256Hash;

const VRF_TRANSCRIPT_LABEL: &[u8] = b"bundle_producer_election";

/// Generates a domain-specific vrf transcript from given global_challenge.
pub fn make_transcript(domain_id: DomainId, global_challenge: &Blake2b256Hash) -> VrfTranscript {
VrfTranscript::new(
VRF_TRANSCRIPT_LABEL,
&[
(b"domain", &domain_id.to_le_bytes()),
(b"global_challenge", global_challenge.as_ref()),
],
)
}

/// Returns the election threshold based on the operator stake proportion and slot probability.
pub fn calculate_threshold(
operator_stake: StakeWeight,
total_domain_stake: StakeWeight,
bundle_slot_probability: (u64, u64),
) -> u128 {
// The calculation is written for not causing the overflow, which might be harder to
// understand, the formula in a readable form is as followes:
//
// bundle_slot_probability.0 operator_stake
// threshold = ------------------------- * --------------------- * u128::MAX
// bundle_slot_probability.1 total_domain_stake
//
// TODO: better to have more audits on this calculation.
u128::MAX / u128::from(bundle_slot_probability.1) * u128::from(bundle_slot_probability.0)
/ total_domain_stake
* operator_stake
}

pub fn is_below_threshold(vrf_output: &VrfOutput, threshold: u128) -> bool {
let vrf_output = u128::from_le_bytes(
vrf_output
.0
.to_bytes()
.split_at(core::mem::size_of::<u128>())
.0
.try_into()
.expect("Slice splitted from VrfOutput must fit into u128; qed"),
);

vrf_output < threshold
}

#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub struct BundleProducerElectionParams<Balance> {
pub current_operators: Vec<OperatorId>,
pub total_domain_stake: Balance,
pub bundle_slot_probability: (u64, u64),
}

#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub enum VrfProofError {
/// Invalid vrf proof.
BadProof,
}

/// Verify the vrf proof generated in the bundle election.
pub(crate) fn verify_vrf_proof(
domain_id: DomainId,
public_key: &OperatorPublicKey,
vrf_signature: &VrfSignature,
global_challenge: &Blake2b256Hash,
) -> Result<(), VrfProofError> {
if !public_key.as_inner_ref().vrf_verify(
&make_transcript(domain_id, global_challenge).into(),
vrf_signature,
) {
return Err(VrfProofError::BadProof);
}

Ok(())
}
Loading

0 comments on commit a98573e

Please sign in to comment.