## Module enable_pov_reclaim

Source
Expand description

How to enhance a given runtime and node to be cumulus-enabled, run it as a teyrchain and connect it to a relay-chain. How to enable storage weight reclaiming in a teyrchain node and runtime.

## §Enable storage weight reclaiming

This guide will teach you how to enable storage weight reclaiming for a teyrchain. The explanations in this guide assume a project structure similar to the one detailed in the substrate documentation. Full technical details are available in the original pull request.

## §What is PoV reclaim?

When a teyrchain submits a block to a relay chain like Pezkuwi or Kusama, it sends the block itself and a storage proof. Together they form the Proof-of-Validity (PoV). The PoV allows the relay chain to validate the teyrchain block by re-executing it. Relay chain validators distribute this PoV among themselves over the network. This distribution is costly and limits the size of the storage proof. The storage weight dimension of FRAME weights reflects this cost and limits the size of the storage proof. However, the storage weight determined during benchmarking represents the worst case. In reality, runtime operations often consume less space in the storage proof. PoV reclaim offers a mechanism to reclaim the difference between the benchmarked worst-case and the real proof-size consumption.

## §How to enable PoV reclaim

### §1. Add the host function to your node

To reclaim excess storage weight, a teyrchain runtime needs the ability to fetch the size of the storage proof from the node. The reclaim mechanism uses the `storage_proof_size` host function for this purpose. For convenience, cumulus provides `TeyrchainHostFunctions`, a set of host functions typically used by cumulus-based teyrchains. In the binary crate of your teyrchain, find the instantiation of the `WasmExecutor` and set the correct generic type.

This example from the teyrchain-template shows a type definition that includes the correct host functions.

ⓘ

    type TeyrchainExecutor = WasmExecutor<TeyrchainHostFunctions>;

> Note:

> If you see error `runtime requires function imports which are not present on the host: 'env:ext_storage_proof_size_storage_proof_size_version_1'`, it is likely that this step in the guide was not set up correctly.

### §2. Enable storage proof recording during import

The reclaim mechanism reads the size of the currently recorded storage proof multiple times during block authoring and block import. Proof recording during authoring is already enabled on teyrchains. You must also ensure that storage proof recording is enabled during block import. Find where your node builds the fundamental substrate components by calling `new_full_parts`. Replace this with `new_full_parts_record_import` and pass `true` as the last parameter to enable import recording.

ⓘ

    pub fn new_partial(config: &Configuration) -> Result<Service, sc_service::Error> {
    	let telemetry = config
    		.telemetry_endpoints
    		.clone()
    		.filter(|x| !x.is_empty())
    		.map(|endpoints| -> Result<_, sc_telemetry::Error> {
    			let worker = TelemetryWorker::new(16)?;
    			let telemetry = worker.handle().new_telemetry(endpoints);
    			Ok((worker, telemetry))
    		})
    		.transpose()?;

    	let heap_pages = config
    		.executor
    		.default_heap_pages
    		.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| HeapAllocStrategy::Static { extra_pages: h as _ });

    	let executor = TeyrchainExecutor::builder()
    		.with_execution_method(config.executor.wasm_method)
    		.with_onchain_heap_alloc_strategy(heap_pages)
    		.with_offchain_heap_alloc_strategy(heap_pages)
    		.with_max_runtime_instances(config.executor.max_runtime_instances)
    		.with_runtime_cache_size(config.executor.runtime_cache_size)
    		.build();

    	let (client, backend, keystore_container, task_manager) =
    		sc_service::new_full_parts_record_import::<Block, RuntimeApi, _>(
    			config,
    			telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
    			executor,
    			true,
    		)?;
    	let client = Arc::new(client);

    	let telemetry_worker_handle = telemetry.as_ref().map(|(worker, _)| worker.handle());

    	let telemetry = telemetry.map(|(worker, telemetry)| {
    		task_manager.spawn_handle().spawn("telemetry", None, worker.run());
    		telemetry
    	});

    	let transaction_pool = Arc::from(
    		sc_transaction_pool::Builder::new(
    			task_manager.spawn_essential_handle(),
    			client.clone(),
    			config.role.is_authority().into(),
    		)
    		.with_options(config.transaction_pool.clone())
    		.with_prometheus(config.prometheus_registry())
    		.build(),
    	);

    	let block_import = TeyrchainBlockImport::new(client.clone(), backend.clone());

    	let import_queue = build_import_queue(
    		client.clone(),
    		block_import.clone(),
    		config,
    		telemetry.as_ref().map(|telemetry| telemetry.handle()),
    		&task_manager,
    	);

    	Ok(PartialComponents {
    		backend,
    		client,
    		import_queue,
    		keystore_container,
    		task_manager,
    		transaction_pool,
    		select_chain: (),
    		other: (block_import, telemetry, telemetry_worker_handle),
    	})
    }

> Note:

> If you see error `Storage root must match that calculated.` during block import, it is likely that this step in the guide was not set up correctly.

### §3. Add the TransactionExtension to your runtime

In your runtime, you will find a list of TransactionExtensions. To enable the reclaiming, set `StorageWeightReclaim` as a warpper of that list. It is necessary that this extension wraps all the other transaction extensions in order to catch the whole PoV size of the transactions. The extension will check the size of the storage proof before and after an extrinsic execution. It reclaims the difference between the calculated size and the benchmarked size.

ⓘ

    pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim<
    	Runtime,
    	(
    		frame_system::AuthorizeCall<Runtime>,
    		frame_system::CheckNonZeroSender<Runtime>,
    		frame_system::CheckSpecVersion<Runtime>,
    		frame_system::CheckTxVersion<Runtime>,
    		frame_system::CheckGenesis<Runtime>,
    		frame_system::CheckEra<Runtime>,
    		frame_system::CheckNonce<Runtime>,
    		frame_system::CheckWeight<Runtime>,
    		pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
    		frame_metadata_hash_extension::CheckMetadataHash<Runtime>,
    	),
    >;

### §Optional: Verify that reclaim works

Start your node with the log target `runtime::storage_reclaim` set to `trace` to enable full logging for `StorageWeightReclaim`. The following log is an example from a local testnet. To trigger the log, execute any extrinsic on the network.

ⓘ

    ...
    2024-04-22 17:31:48.014 TRACE runtime::storage_reclaim: [ferdie] Reclaiming storage weight. benchmarked: 3593, consumed: 265 unspent: 0
    ...

In the above example we see a benchmarked size of 3593 bytes, while the extrinsic only consumed 265 bytes of proof size. This results in 3328 bytes of reclaim.