Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions Cargo.lock

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

12 changes: 6 additions & 6 deletions crates/sp-executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use sp_runtime::traits::{BlakeTwo256, Hash as HashT};
use sp_runtime::{OpaqueExtrinsic, RuntimeDebug};
use sp_std::vec::Vec;
use sp_trie::StorageProof;
use subspace_core_primitives::Randomness;
use subspace_core_primitives::{Randomness, Sha256Hash};
use subspace_runtime_primitives::AccountId;

/// Header of transaction bundle.
Expand Down Expand Up @@ -98,12 +98,12 @@ impl<Extrinsic: sp_runtime::traits::Extrinsic + Encode> From<Bundle<Extrinsic>>
pub struct ExecutionReceipt<Hash> {
/// Primary block hash.
pub primary_hash: H256,
/// Secondary block hash?
/// Secondary block hash.
pub secondary_hash: Hash,
/// State root after finishing the execution.
pub state_root: Hash,
/// Merkle root of the execution.
pub state_transition_root: H256,
/// List of storage roots collected during the block execution.
pub trace: Vec<Hash>,
/// The merkle root of `trace`.
pub trace_root: Sha256Hash,
}

impl<Hash: Encode> ExecutionReceipt<Hash> {
Expand Down
16 changes: 16 additions & 0 deletions cumulus/client/cirrus-executor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@ cumulus-client-consensus-common = { path = "../consensus/common" }

# Other dependencies
codec = { package = "parity-scale-codec", version = "2.3.0", features = [ "derive" ] }
crossbeam = "0.8"
futures = { version = "0.3.19", features = ["compat"] }
futures-timer = "3.0.1"
rand = "0.8.4"
rand_chacha = "0.3.1"
merkletree = "0.21.0"
parking_lot = "0.12.0"
tracing = "0.1.25"
thiserror = "1.0.29"
tokio = "1.10"

polkadot-overseer = { path = "../../../polkadot/node/overseer" }
polkadot-node-subsystem = { path = "../../../polkadot/node/subsystem" }
Expand All @@ -43,5 +47,17 @@ sp-executor = { path = "../../../crates/sp-executor" }
subspace-core-primitives = { path = "../../../crates/subspace-core-primitives" }
subspace-runtime-primitives = { path = "../../../crates/subspace-runtime-primitives" }

# Ugly workaround for https://github.com/rust-lang/cargo/issues/1197
[target.'cfg(any(target_os = "linux", target_os = "macos", all(target_os = "windows", target_env = "gnu")))'.dependencies.sha2]
features = ["asm"]
version = "0.10.0"

# Ugly workaround for https://github.com/rust-lang/cargo/issues/1197
# `asm` feature is not supported on Windows except with GNU toolchain
[target.'cfg(not(any(target_os = "linux", target_os = "macos", all(target_os = "windows", target_env = "gnu"))))'.dependencies.sha2]
version = "0.10.0"

[dev-dependencies]
sp-keyring = { git = "https://github.com/paritytech/substrate", rev = "2c4549689dbb86c23725dac2f82af35faa07c9f6" }
substrate-test-runtime = { path = "../../../substrate/substrate-test-runtime" }
substrate-test-runtime-client = { path = "../../../substrate/substrate-test-runtime-client" }
193 changes: 193 additions & 0 deletions cumulus/client/cirrus-executor/src/aux_schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
//! Schema for executor in the aux-db.

use codec::{Decode, Encode};
use sc_client_api::backend::AuxStore;
use sp_blockchain::{Error as ClientError, Result as ClientResult};
use sp_executor::ExecutionReceipt;
use sp_runtime::{
traits::{Block as BlockT, Header as HeaderT, One, Saturating},
SaturatedConversion,
};

const EXECUTION_RECEIPT_KEY: &[u8] = b"execution_receipt";
const EXECUTION_RECEIPT_START: &[u8] = b"execution_receipt_start";
const EXECUTION_RECEIPT_BLOCK_NUMBER: &[u8] = b"execution_receipt_block_number";
/// Prune the execution receipts when they reach this number.
const PRUNING_DEPTH: u64 = 1000;

fn execution_receipt_key(block_hash: impl Encode) -> Vec<u8> {
(EXECUTION_RECEIPT_KEY, block_hash).encode()
}

fn load_decode<Backend: AuxStore, T: Decode>(
backend: &Backend,
key: &[u8],
) -> ClientResult<Option<T>> {
match backend.get_aux(key)? {
None => Ok(None),
Some(t) => T::decode(&mut &t[..])
.map_err(|e| {
ClientError::Backend(format!("Executor DB is corrupted. Decode error: {}", e))
})
.map(Some),
}
}

/// Write the execution receipt of a block to aux storage, optionally prune the receipts that are
/// too old.
pub(super) fn write_execution_receipt<Backend: AuxStore, Block: BlockT>(
backend: &Backend,
block_hash: Block::Hash,
block_number: <<Block as BlockT>::Header as HeaderT>::Number,
execution_receipt: &ExecutionReceipt<Block::Hash>,
) -> Result<(), sp_blockchain::Error> {
let block_number_key = (EXECUTION_RECEIPT_BLOCK_NUMBER, block_number).encode();
let mut hashes_at_block_number =
load_decode::<_, Vec<Block::Hash>>(backend, block_number_key.as_slice())?
.unwrap_or_default();
hashes_at_block_number.push(block_hash);

let first_saved_receipt = load_decode::<_, <<Block as BlockT>::Header as HeaderT>::Number>(
backend,
EXECUTION_RECEIPT_START,
)?
.unwrap_or(block_number);

let mut new_first_saved_receipt = first_saved_receipt;

let keys_to_delete = if block_number - first_saved_receipt >= PRUNING_DEPTH.saturated_into() {
new_first_saved_receipt = block_number.saturating_sub((PRUNING_DEPTH - 1).saturated_into());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In these cases I would honestly prefer .expect() since we don't expect these numbers to ever be beyond u32::MAX.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to refactor it directly as I don't see another style that is satisfying to me using expect() :P.


let mut keys_to_delete = vec![];
let mut to_delete_start = first_saved_receipt;
while to_delete_start < new_first_saved_receipt {
let delete_block_number_key =
(EXECUTION_RECEIPT_BLOCK_NUMBER, to_delete_start).encode();
if let Some(hashes_to_delete) =
load_decode::<_, Vec<Block::Hash>>(backend, delete_block_number_key.as_slice())?
{
keys_to_delete.extend(
hashes_to_delete.into_iter().map(|h| (EXECUTION_RECEIPT_KEY, h).encode()),
);
keys_to_delete.push(delete_block_number_key);
}
to_delete_start = to_delete_start.saturating_add(One::one());
}

keys_to_delete
} else {
vec![]
};

backend.insert_aux(
&[
(execution_receipt_key(block_hash).as_slice(), execution_receipt.encode().as_slice()),
(block_number_key.as_slice(), hashes_at_block_number.encode().as_slice()),
((EXECUTION_RECEIPT_START, new_first_saved_receipt.encode().as_slice())),
],
&keys_to_delete.iter().map(|k| &k[..]).collect::<Vec<&[u8]>>()[..],
)
}

/// Load the execution receipt associated with a block.
pub(super) fn load_execution_receipt<Backend: AuxStore, Block: BlockT>(
backend: &Backend,
block_hash: Block::Hash,
) -> ClientResult<Option<ExecutionReceipt<Block::Hash>>> {
load_decode(backend, execution_receipt_key(block_hash).as_slice())
}

pub(super) fn target_receipt_is_pruned<Block: BlockT>(
current_block: <<Block as BlockT>::Header as HeaderT>::Number,
target_block: <<Block as BlockT>::Header as HeaderT>::Number,
) -> bool {
current_block - target_block >= PRUNING_DEPTH.saturated_into()
}

#[cfg(test)]
mod tests {
use super::*;
use sp_core::hash::H256;
use substrate_test_runtime::{Block, BlockNumber, Hash};

type ExecutionReceipt = sp_executor::ExecutionReceipt<Hash>;

fn create_execution_receipt() -> ExecutionReceipt {
ExecutionReceipt {
primary_hash: H256::random(),
secondary_hash: H256::random(),
trace: Default::default(),
trace_root: Default::default(),
}
}

#[test]
fn prune_execution_receipt_works() {
let client = substrate_test_runtime_client::new();

let receipt_start = || {
load_decode::<_, BlockNumber>(&client, EXECUTION_RECEIPT_START.to_vec().as_slice())
.unwrap()
};

let hashes_at = |number: BlockNumber| {
load_decode::<_, Vec<Hash>>(
&client,
(EXECUTION_RECEIPT_BLOCK_NUMBER, number).encode().as_slice(),
)
.unwrap()
};

let receipt_at =
|block_hash: Hash| load_execution_receipt::<_, Block>(&client, block_hash).unwrap();

let write_receipt_at = |hash: Hash, number: BlockNumber, receipt: &ExecutionReceipt| {
write_execution_receipt::<_, Block>(&client, hash, number, receipt).unwrap()
};

assert_eq!(receipt_start(), None);

// Create PRUNING_DEPTH receipts.
let block_hash_list = (1..=PRUNING_DEPTH)
.map(|block_number| {
let receipt = create_execution_receipt();
let block_hash = Hash::random();
write_receipt_at(block_hash, block_number, &receipt);
assert_eq!(receipt_at(block_hash), Some(receipt));
assert_eq!(hashes_at(block_number), Some(vec![block_hash]));
assert_eq!(receipt_start(), Some(1));
block_hash
})
.collect::<Vec<_>>();

assert!(!target_receipt_is_pruned::<Block>(PRUNING_DEPTH, 1));

// Create PRUNING_DEPTH + 1 receipt.
let block_hash = Hash::random();
assert!(receipt_at(block_hash).is_none());
write_receipt_at(block_hash, PRUNING_DEPTH + 1, &create_execution_receipt());
assert!(receipt_at(block_hash).is_some());
// ER of block #1 should be pruned.
assert!(receipt_at(block_hash_list[0]).is_none());
// block number mapping should be pruned as well.
assert!(hashes_at(1).is_none());
assert!(target_receipt_is_pruned::<Block>(PRUNING_DEPTH + 1, 1));
assert_eq!(receipt_start(), Some(2));

// Create PRUNING_DEPTH + 2 receipt.
let block_hash = Hash::random();
write_receipt_at(block_hash, PRUNING_DEPTH + 2, &create_execution_receipt());
assert!(receipt_at(block_hash).is_some());
// ER of block #2 should be pruned.
assert!(receipt_at(block_hash_list[1]).is_none());
assert!(target_receipt_is_pruned::<Block>(PRUNING_DEPTH + 2, 2));
assert!(!target_receipt_is_pruned::<Block>(PRUNING_DEPTH + 2, 3));
assert_eq!(receipt_start(), Some(3));

// Multiple hashes attached to the block #(PRUNING_DEPTH + 2)
let block_hash2 = Hash::random();
write_receipt_at(block_hash2, PRUNING_DEPTH + 2, &create_execution_receipt());
assert!(receipt_at(block_hash2).is_some());
assert_eq!(hashes_at(PRUNING_DEPTH + 2), Some(vec![block_hash, block_hash2]));
}
}
12 changes: 5 additions & 7 deletions cumulus/client/cirrus-executor/src/bundler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,18 @@ use subspace_runtime_primitives::Hash as PHash;

use super::{Executor, LOG_TARGET};

impl<Block, BS, RA, Client, TransactionPool, Backend, CIDP>
Executor<Block, BS, RA, Client, TransactionPool, Backend, CIDP>
impl<Block, Client, TransactionPool, Backend, CIDP>
Executor<Block, Client, TransactionPool, Backend, CIDP>
where
Block: BlockT,
Client: sp_blockchain::HeaderBackend<Block>,
BS: BlockBackend<Block>,
Backend: sc_client_api::Backend<Block>,
RA: ProvideRuntimeApi<Block>,
RA::Api: SecondaryApi<Block, AccountId>
Client: sp_blockchain::HeaderBackend<Block> + BlockBackend<Block> + ProvideRuntimeApi<Block>,
Client::Api: SecondaryApi<Block, AccountId>
+ sp_block_builder::BlockBuilder<Block>
+ sp_api::ApiExt<
Block,
StateBackend = sc_client_api::backend::StateBackendFor<Backend, Block>,
>,
Backend: sc_client_api::Backend<Block>,
TransactionPool: sc_transaction_pool_api::TransactionPool<Block = Block>,
{
pub(super) async fn produce_bundle_impl(
Expand Down
Loading