Skip to content

Commit fb13b03

Browse files
committed
Reworked CID vs content_hash handling
1 parent 2815b20 commit fb13b03

File tree

6 files changed

+203
-44
lines changed

6 files changed

+203
-44
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ westend-runtime-constants = { git = "https://github.com/paritytech/polkadot-sdk.
2020
array-bytes = { version = "6.1" }
2121
bp-test-utils = { git = "https://github.com/paritytech/polkadot-sdk.git", rev = "56e4b10bbbbcefcf9e4fe879c88c68902181fc4a" }
2222
cfg-if = { version = "1.0" }
23+
cid = { version = "0.11.1", default-features = false }
2324
clap = { version = "4.2.5" }
2425
codec = { package = "parity-scale-codec", version = "3.7.5", default-features = false }
2526
futures = { version = "0.3.21" }

pallets/transaction-storage/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"]
1414

1515
[dependencies]
1616
array-bytes = { optional = true, workspace = true }
17+
cid = { features = ["alloc"], workspace = true }
1718
codec = { workspace = true }
1819
log = { workspace = true, default-features = true }
1920
scale-info = { features = ["derive"], workspace = true }
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Copyright (C) Parity Technologies (UK) Ltd.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
//! Utilities for working with CIDs (Content Identifiers).
17+
//!
18+
//! This module provides types and functions to compute CIDs for raw data or
19+
//! DAG blocks using supported hashing algorithms and codecs.
20+
21+
use crate::LOG_TARGET;
22+
use cid::{multihash::Multihash, CidGeneric};
23+
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
24+
use polkadot_sdk_frame::deps::sp_io;
25+
26+
/// 32-byte hash of a stored blob of data.
27+
pub type ContentHash = [u8; 32];
28+
29+
/// CIDv1 serialized bytes (codec + multihash(ContentHash)).
30+
pub type Cid = Vec<u8>;
31+
32+
/// Type alias representing a CID codec (e.g., raw = 0x55, dag-pb = 0x70).
33+
pub type CidCodec = u64;
34+
35+
/// Supported hashing algorithms for computing CIDs.
36+
#[derive(
37+
Clone,
38+
PartialEq,
39+
Eq,
40+
Encode,
41+
Debug,
42+
Decode,
43+
DecodeWithMemTracking,
44+
scale_info::TypeInfo,
45+
MaxEncodedLen,
46+
)]
47+
pub enum HashingAlgorithm {
48+
/// Blake2b-256 hash function.
49+
Blake2b256,
50+
/// SHA2-256 hash function.
51+
Sha2_256,
52+
/// Keccak-256 hash function.
53+
Keccak256,
54+
}
55+
56+
impl HashingAlgorithm {
57+
/// Compute the hash of the given data using the selected algorithm.
58+
pub fn hash(&self, data: &[u8]) -> ContentHash {
59+
match self {
60+
HashingAlgorithm::Blake2b256 => sp_io::hashing::blake2_256(data),
61+
HashingAlgorithm::Sha2_256 => sp_io::hashing::sha2_256(data),
62+
HashingAlgorithm::Keccak256 => sp_io::hashing::keccak_256(data),
63+
}
64+
}
65+
66+
/// Return the multihash code corresponding to this hashing algorithm.
67+
///
68+
/// These codes follow the [multihash table](https://github.com/multiformats/multicodec/blob/master/table.csv):
69+
/// - Blake2b-256 = 0xb2 (178)
70+
/// - SHA2-256 = 0x12 (18)
71+
/// - Keccak-256 = 0x1b (27)
72+
pub fn multihash_code(&self) -> u64 {
73+
match self {
74+
HashingAlgorithm::Blake2b256 => 0xb2,
75+
HashingAlgorithm::Sha2_256 => 0x12,
76+
HashingAlgorithm::Keccak256 => 0x1b,
77+
}
78+
}
79+
}
80+
81+
/// Configuration for generating a CID.
82+
#[derive(
83+
Clone,
84+
PartialEq,
85+
Eq,
86+
Encode,
87+
Debug,
88+
Decode,
89+
DecodeWithMemTracking,
90+
scale_info::TypeInfo,
91+
MaxEncodedLen,
92+
)]
93+
pub struct CidConfig {
94+
/// CID codec (e.g., raw = 0x55, dag-pb = 0x70).
95+
pub codec: CidCodec,
96+
/// Hashing algorithm to use for computing the content hash.
97+
pub hashing: HashingAlgorithm,
98+
}
99+
100+
/// Representation of a generated CID.
101+
pub struct CidData {
102+
/// 32-byte content hash of the input data.
103+
pub content_hash: ContentHash,
104+
/// Hashing algorithm used.
105+
pub hashing: HashingAlgorithm,
106+
/// Codec used for the CIDv1.
107+
pub codec: CidCodec,
108+
/// CIDv1 serialized bytes (codec + multihash(content_hash)).
109+
pub cid: Cid,
110+
}
111+
112+
/// Compute a CIDv1 for the given data with optional configuration.
113+
///
114+
/// If no configuration is provided, defaults are:
115+
/// - Codec: raw (0x55)
116+
/// - Hashing: Blake2b-256
117+
///
118+
/// # Errors
119+
/// Returns `Err(())` if multihash wrapping fails.
120+
pub fn calculate_cid(data: &[u8], config: Option<CidConfig>) -> Result<CidData, ()> {
121+
// Determine hashing algorithm and codec
122+
let (hashing, codec) = if let Some(config) = config {
123+
(config.hashing, config.codec)
124+
} else {
125+
// Defaults: raw codec (0x55) and Blake2b-256 hash
126+
(HashingAlgorithm::Blake2b256, 0x55)
127+
};
128+
129+
// Hash the data
130+
let content_hash = hashing.hash(data);
131+
132+
// Wrap hash into a multihash
133+
let multihash_code = hashing.multihash_code();
134+
let mh = Multihash::<32>::wrap(multihash_code, &content_hash).map_err(|e| {
135+
log::warn!(
136+
target: LOG_TARGET,
137+
"Failed to create Multihash for content_hash: {content_hash:?}, multihash_code: {multihash_code:?}, error: {e:?}"
138+
);
139+
()
140+
})?;
141+
142+
// Create CIDv1 bytes
143+
let cid_bytes = CidGeneric::<32>::new_v1(codec, mh).to_bytes();
144+
145+
// Return all relevant data
146+
Ok(CidData { content_hash, hashing, codec, cid: cid_bytes })
147+
}
148+
149+
// TODO: add here more tests for compatibility.
150+

pallets/transaction-storage/src/extension.rs

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
//! Custom transaction extension for the transaction storage pallet.
1919
20-
use crate::{Call, CidCodecForStore, Config, LOG_TARGET};
20+
use crate::{cids::CidConfig, Call, CidConfigForStore, Config, LOG_TARGET};
2121
use codec::{Decode, DecodeWithMemTracking, Encode};
2222
use core::{fmt, marker::PhantomData};
2323
use polkadot_sdk_frame::{
@@ -26,32 +26,23 @@ use polkadot_sdk_frame::{
2626
traits::Implication,
2727
};
2828

29-
/// Type alias representing a CID codec.
30-
pub type CidCodec = u64;
31-
32-
/// Temporarily tracks provided optional CID codec.
33-
#[derive(Default)]
34-
pub struct CidCodecContext {
35-
pub codec: Option<CidCodec>,
36-
}
37-
3829
/// `TransactionExtension` implementation that provides optional `CidCodec` for the `store`
3930
/// extrinsic.
4031
#[derive(Clone, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, scale_info::TypeInfo)]
4132
#[scale_info(skip_type_params(T))]
42-
pub struct ProvideCidCodec<T>(pub Option<CidCodec>, PhantomData<T>);
33+
pub struct ProvideCidConfig<T>(pub Option<CidConfig>, PhantomData<T>);
4334

44-
impl<T> ProvideCidCodec<T> {
45-
/// Create a new `ProvideCidCodec` instance.
46-
pub fn new(cid_codec: Option<CidCodec>) -> Self {
47-
Self(cid_codec, Default::default())
35+
impl<T> ProvideCidConfig<T> {
36+
/// Create a new `ProvideCidConfig` instance.
37+
pub fn new(config: Option<CidConfig>) -> Self {
38+
Self(config, Default::default())
4839
}
4940
}
5041

51-
impl<T: Config + Send + Sync> fmt::Debug for ProvideCidCodec<T> {
42+
impl<T: Config + Send + Sync> fmt::Debug for ProvideCidConfig<T> {
5243
#[cfg(feature = "std")]
5344
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54-
write!(f, "ProvideCidCodec({:?})", self.0)
45+
write!(f, "ProvideCidConfig({:?})", self.0)
5546
}
5647

5748
#[cfg(not(feature = "std"))]
@@ -60,18 +51,18 @@ impl<T: Config + Send + Sync> fmt::Debug for ProvideCidCodec<T> {
6051
}
6152
}
6253

63-
impl<T: Config + Send + Sync> TransactionExtension<T::RuntimeCall> for ProvideCidCodec<T>
54+
impl<T: Config + Send + Sync> TransactionExtension<T::RuntimeCall> for ProvideCidConfig<T>
6455
where
6556
<T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>>,
6657
{
67-
const IDENTIFIER: &'static str = "ProvideCidCodec";
58+
const IDENTIFIER: &'static str = "ProvideCidConfig";
6859

6960
type Implicit = ();
7061
fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> {
7162
Ok(())
7263
}
7364

74-
type Val = Option<CidCodec>;
65+
type Val = Option<CidConfig>;
7566
type Pre = ();
7667

7768
fn weight(&self, _call: &T::RuntimeCall) -> Weight {
@@ -88,9 +79,9 @@ where
8879
_inherited_implication: &impl Implication,
8980
_source: TransactionSource,
9081
) -> ValidateResult<Self::Val, T::RuntimeCall> {
91-
match (self.0, call.is_sub_type()) {
92-
(Some(cid_codec), Some(Call::store { .. })) =>
93-
Ok((Default::default(), Some(cid_codec), origin)),
82+
match (self.0.as_ref(), call.is_sub_type()) {
83+
(Some(cid_config), Some(Call::store { .. })) =>
84+
Ok((Default::default(), Some(cid_config.clone()), origin)),
9485
(Some(_), _) => {
9586
// All other calls are invalid with cid_codec.
9687
Err(InvalidTransaction::Call.into())
@@ -108,10 +99,17 @@ where
10899
_len: usize,
109100
) -> Result<Self::Pre, TransactionValidityError> {
110101
log::error!(target: LOG_TARGET, "prepare: {val:?}");
111-
if let Some(cid_codec) = val {
112-
CidCodecForStore::<T>::set(Some(cid_codec));
113-
114-
// TODO: just attempt, not working, will remove.
102+
if let Some(cid_config) = val {
103+
// Let's store the codec in the intermediary storage, which will be cleared by the store
104+
// extrinsic.
105+
CidConfigForStore::<T>::set(Some(cid_config));
106+
107+
// TODO: just attempt with dispatch `with_context`, not working, will remove.
108+
// /// Temporarily tracks provided optional CID codec.
109+
// #[derive(Default)]
110+
// pub struct CidCodecContext {
111+
// pub codec: Option<CidCodec>,
112+
// }
115113
// Put cid codec to the dispatch context
116114
// with_context::<CidCodecContext, _>(|v| {
117115
// let context = v.or_default();

0 commit comments

Comments
 (0)