Skip to content

Commit

Permalink
Merge pull request #923 from subspace/core-domain-bundle-solution-ver…
Browse files Browse the repository at this point in the history
…ification

Core domain bundle solution verification
  • Loading branch information
liuchengxu authored Nov 17, 2022
2 parents c080a1e + a4ad260 commit 132b820
Show file tree
Hide file tree
Showing 13 changed files with 354 additions and 89 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

90 changes: 16 additions & 74 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,7 @@ use frame_support::traits::Get;
use frame_system::offchain::SubmitTransaction;
pub use pallet::*;
use sp_core::H256;
use sp_domains::bundle_election::{
calculate_bundle_election_threshold, derive_bundle_election_solution,
is_election_solution_within_threshold, read_bundle_election_params, verify_vrf_proof,
BundleElectionParams,
};
use sp_domains::bundle_election::{verify_system_bundle_solution, verify_vrf_proof};
use sp_domains::{
BundleEquivocationProof, ExecutionReceipt, FraudProof, InvalidTransactionCode,
InvalidTransactionProof, ProofOfElection, SignedOpaqueBundle,
Expand Down Expand Up @@ -112,14 +108,8 @@ mod pallet {
BadStateRoot,
/// The type of state root is not H256.
StateRootNotH256,
/// Failed to derive the bundle election solution.
FailedToDeriveBundleElectionSolution,
/// Can not retrieve the state needed from the storage proof.
BadStorageProof,
/// Bundle author is not found in the authority set.
AuthorityNotFound,
/// Election solution does not satisfy the threshold.
InvalidElectionSolution,
/// Invalid system bundle election solution.
BadElectionSolution,
/// An invalid execution receipt found in the bundle.
Receipt(ExecutionReceiptError),
}
Expand Down Expand Up @@ -578,37 +568,12 @@ impl<T: Config> Pallet<T> {
Ok(())
}

fn validate_bundle_election(
receipts: &[ExecutionReceipt<T::BlockNumber, T::Hash, T::SecondaryHash>],
proof_of_election: &ProofOfElection<T::SecondaryHash>,
) -> Result<(), BundleError> {
verify_vrf_proof(
&proof_of_election.executor_public_key,
&proof_of_election.vrf_output,
&proof_of_election.vrf_proof,
&proof_of_election.global_challenge,
)
.map_err(|_| BundleError::BadVrfProof)?;

// TODO: validate core domain bundle solution.
if proof_of_election.domain_id.is_system() {
Self::validate_system_bundle_solution(receipts, proof_of_election)?;
}

Ok(())
}

fn validate_system_bundle_solution(
receipts: &[ExecutionReceipt<T::BlockNumber, T::Hash, T::SecondaryHash>],
proof_of_election: &ProofOfElection<T::SecondaryHash>,
) -> Result<(), BundleError> {
let ProofOfElection {
domain_id,
vrf_output,
executor_public_key,
global_challenge,
state_root,
storage_proof,
block_number,
block_hash,
..
Expand Down Expand Up @@ -643,41 +608,8 @@ impl<T: Config> Pallet<T> {
let state_root = H256::decode(&mut state_root.encode().as_slice())
.map_err(|_| BundleError::StateRootNotH256)?;

let BundleElectionParams {
authorities,
total_stake_weight,
slot_probability,
} = read_bundle_election_params(storage_proof.clone(), &state_root)
.map_err(|_| BundleError::BadStorageProof)?;

let stake_weight = authorities
.iter()
.find_map(|(authority, weight)| {
if authority == executor_public_key {
Some(weight)
} else {
None
}
})
.ok_or(BundleError::AuthorityNotFound)?;

let election_solution = derive_bundle_election_solution(
*domain_id,
*vrf_output,
executor_public_key,
global_challenge,
)
.map_err(|_| BundleError::FailedToDeriveBundleElectionSolution)?;

let threshold = calculate_bundle_election_threshold(
*stake_weight,
total_stake_weight,
slot_probability,
);

if !is_election_solution_within_threshold(election_solution, threshold) {
return Err(BundleError::InvalidElectionSolution);
}
verify_system_bundle_solution(proof_of_election, state_root)
.map_err(|_| BundleError::BadElectionSolution)?;

Ok(())
}
Expand Down Expand Up @@ -747,7 +679,17 @@ impl<T: Config> Pallet<T> {
return Err(BundleError::BadSignature);
}

Self::validate_bundle_election(&bundle.receipts, proof_of_election)?;
verify_vrf_proof(
&proof_of_election.executor_public_key,
&proof_of_election.vrf_output,
&proof_of_election.vrf_proof,
&proof_of_election.global_challenge,
)
.map_err(|_| BundleError::BadVrfProof)?;

if proof_of_election.domain_id.is_system() {
Self::validate_system_bundle_solution(&bundle.receipts, proof_of_election)?;
}

Self::validate_execution_receipts(&bundle.receipts).map_err(BundleError::Receipt)?;

Expand Down
87 changes: 85 additions & 2 deletions crates/sp-domains/src/bundle_election.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::{DomainId, ExecutorPublicKey, StakeWeight};
use crate::{DomainId, ExecutorPublicKey, ProofOfElection, StakeWeight};
use merlin::Transcript;
use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
use schnorrkel::vrf::{VRFOutput, VRFProof, VRF_OUTPUT_LENGTH};
use schnorrkel::SignatureResult;
use schnorrkel::{SignatureError, SignatureResult};
use sp_core::H256;
#[cfg(feature = "std")]
use sp_keystore::vrf::{VRFTranscriptData, VRFTranscriptValue};
Expand Down Expand Up @@ -226,3 +226,86 @@ pub fn read_bundle_election_params(
slot_probability,
})
}

#[derive(Debug)]
pub enum BundleSolutionError {
/// Can not retrieve the state needed from the storage proof.
BadStorageProof(ReadBundleElectionParamsError),
/// Bundle author is not found in the authority set.
AuthorityNotFound,
/// Failed to derive the bundle election solution.
FailedToDeriveBundleElectionSolution(SignatureError),
/// Election solution does not satisfy the threshold.
InvalidElectionSolution,
}

pub fn verify_system_bundle_solution<SecondaryHash>(
proof_of_election: &ProofOfElection<SecondaryHash>,
verified_state_root: H256,
) -> Result<(), BundleSolutionError> {
let ProofOfElection {
domain_id,
vrf_output,
executor_public_key,
global_challenge,
storage_proof,
..
} = proof_of_election;

let BundleElectionParams {
authorities,
total_stake_weight,
slot_probability,
} = read_bundle_election_params(storage_proof.clone(), &verified_state_root)
.map_err(BundleSolutionError::BadStorageProof)?;

let stake_weight = authorities
.iter()
.find_map(|(authority, weight)| {
if authority == executor_public_key {
Some(weight)
} else {
None
}
})
.ok_or(BundleSolutionError::AuthorityNotFound)?;

verify_bundle_solution_threshold(
*domain_id,
*vrf_output,
*stake_weight,
total_stake_weight,
slot_probability,
executor_public_key,
global_challenge,
)?;

Ok(())
}

pub fn verify_bundle_solution_threshold(
domain_id: DomainId,
vrf_output: [u8; VRF_OUTPUT_LENGTH],
stake_weight: StakeWeight,
total_stake_weight: StakeWeight,
slot_probability: (u64, u64),
executor_public_key: &ExecutorPublicKey,
global_challenge: &Blake2b256Hash,
) -> Result<(), BundleSolutionError> {
let election_solution = derive_bundle_election_solution(
domain_id,
vrf_output,
executor_public_key,
global_challenge,
)
.map_err(BundleSolutionError::FailedToDeriveBundleElectionSolution)?;

let threshold =
calculate_bundle_election_threshold(stake_weight, total_stake_weight, slot_probability);

if !is_election_solution_within_threshold(election_solution, threshold) {
return Err(BundleSolutionError::InvalidElectionSolution);
}

Ok(())
}
7 changes: 7 additions & 0 deletions crates/sp-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ pub struct ProofOfElection<SecondaryHash> {
pub block_number: BlockNumber,
/// Block hash corresponding to the `block_number` above.
pub block_hash: SecondaryHash,
/// Block hash of the core domain block at which the proof of election was created.
pub core_block_hash: Option<SecondaryHash>,
/// Core domain state root corresponding to the `core_block_hash` above.
pub core_state_root: Option<SecondaryHash>,
}

impl<SecondaryHash: Default> ProofOfElection<SecondaryHash> {
Expand All @@ -236,6 +240,8 @@ impl<SecondaryHash: Default> ProofOfElection<SecondaryHash> {
storage_proof: StorageProof::empty(),
block_number: Default::default(),
block_hash: Default::default(),
core_block_hash: None,
core_state_root: None,
}
}
}
Expand Down Expand Up @@ -338,6 +344,7 @@ pub struct ExecutionReceipt<Number, Hash, SecondaryHash> {
pub primary_number: Number,
/// Primary block hash.
pub primary_hash: Hash,
// TODO: rename to `domain_hash`.
/// Secondary block hash.
pub secondary_hash: SecondaryHash,
/// List of storage roots collected during the block execution.
Expand Down
31 changes: 27 additions & 4 deletions domains/client/domain-executor/src/bundle_election_solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,33 @@ where
);

if is_election_solution_within_threshold(election_solution, threshold) {
let storage_keys = well_known_keys::bundle_election_storage_keys(domain_id);
// TODO: bench how large the storage proof we can afford and try proving a single
// electioned executor storage instead of the whole authority set.
let storage_proof = self
.client
.read_proof(best_hash, &mut storage_keys.iter().map(|s| s.as_slice()))?;
let storage_proof = if domain_id.is_system() {
let storage_keys = well_known_keys::bundle_election_storage_keys(domain_id);
self.client
.read_proof(best_hash, &mut storage_keys.iter().map(|s| s.as_slice()))?
} else if domain_id.is_core() {
let storage_keys = self
.client
.runtime_api()
.core_bundle_election_storage_keys(
&best_block_id,
domain_id,
authority_id.clone(),
)?
.ok_or_else(|| {
sp_blockchain::Error::Backend(
"Empty core bundle election storage keys".to_string(),
)
})?;
self.client
.read_proof(best_hash, &mut storage_keys.iter().map(|s| s.as_slice()))?
} else {
return Err(sp_blockchain::Error::Application(Box::from(
"Only system and core domain are supported".to_string(),
)));
};

let state_root = *self
.client
Expand All @@ -133,6 +154,8 @@ where
storage_proof,
block_number: best_number,
block_hash: best_hash,
core_block_hash: None,
core_state_root: None,
};

return Ok(Some(proof_of_election));
Expand Down
15 changes: 14 additions & 1 deletion domains/client/domain-executor/src/core_bundle_producer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use sp_domains::{
};
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
use sp_runtime::generic::BlockId;
use sp_runtime::traits::{BlakeTwo256, Block as BlockT, Hash as HashT, Zero};
use sp_runtime::traits::{BlakeTwo256, Block as BlockT, Hash as HashT, Header as HeaderT, Zero};
use sp_runtime::RuntimeAppPublic;
use std::marker::PhantomData;
use std::sync::Arc;
Expand Down Expand Up @@ -143,9 +143,12 @@ where
to_sign.as_ref(),
) {
Ok(Some(signature)) => {
let best_hash = self.client.info().best_hash;

let as_core_block_hash = |system_block_hash: SBlock::Hash| {
Block::Hash::decode(&mut system_block_hash.encode().as_slice()).unwrap()
};

let signed_bundle = SignedBundle {
bundle,
proof_of_election: ProofOfElection {
Expand All @@ -158,6 +161,16 @@ where
storage_proof: proof_of_election.storage_proof,
block_number: proof_of_election.block_number,
block_hash: as_core_block_hash(proof_of_election.block_hash),
// TODO: override the core block info, see if there is a nicer way
// later.
core_block_hash: Some(best_hash),
core_state_root: Some(
*self
.client
.header(BlockId::Hash(best_hash))?
.expect("Best block header must exist; qed")
.state_root(),
),
},
signature: ExecutorSignature::decode(&mut signature.as_slice()).map_err(
|err| {
Expand Down
2 changes: 2 additions & 0 deletions domains/pallets/domain-registry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ sp-domains = { version = "0.1.0", path = "../../../crates/sp-domains", default-f
sp-executor-registry = { version = "0.1.0", path = "../../primitives/executor-registry", default-features = false }
sp-runtime = { git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f", default-features = false }
sp-std = { git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f", default-features = false }
sp-trie = { git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f", default-features = false }

[dev-dependencies]
pallet-balances = { git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" }
Expand All @@ -44,5 +45,6 @@ std = [
"sp-executor-registry/std",
"sp-runtime/std",
"sp-std/std",
"sp-trie/std",
]
try-runtime = ["frame-support/try-runtime"]
Loading

0 comments on commit 132b820

Please sign in to comment.