1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// Copyright (C) 2021 Subspace Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Defines FeedProcessor and its types

#[cfg(not(feature = "std"))]
extern crate alloc;

use crate::CallObject;
#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use codec::{Compact, CompactLen, Decode, Encode};
use sp_runtime::{DispatchError, DispatchResult};
use subspace_core_primitives::Blake3Hash;

/// Holds the offset to some portion of data within/or the object
#[derive(Debug)]
pub enum FeedObjectMapping {
    /// Maps the object or some data within the object at the specific offset.
    /// The key is derived from the content.
    Content { offset: u32 },

    /// Maps the object or some data within the object at the specific offset.
    /// The key provided is namespaced to feed to avoid collisions
    Custom { key: Vec<u8>, offset: u32 },
}

impl FeedObjectMapping {
    pub(crate) fn try_into_call_object<FeedID: Encode, Hasher: Fn(&[u8]) -> Blake3Hash>(
        self,
        feed_id: FeedID,
        object: &[u8],
        hasher: Hasher,
    ) -> Option<CallObject> {
        match self {
            // If this is a custom key, then name space the key.
            FeedObjectMapping::Custom { key, offset } => {
                let mut data = feed_id.encode();
                data.extend_from_slice(&key);
                Some(CallObject {
                    key: hasher(&data),
                    offset,
                })
            }
            // For content, we try to extract the content to derive the key
            FeedObjectMapping::Content { offset } => {
                // If offset is 0, then we want to map the entire object.
                // Since the object is already decoded, no need to decode it further
                let key = if offset == 0 {
                    hasher(object)
                } else {
                    // This is referring to some content within the object that is encoded.
                    // Move the offset back by the encoded bytes of object to get the right offset since the object is already decoded.
                    let offset = offset
                        .saturating_sub(Compact::<u32>::compact_len(&(object.len() as u32)) as u32);
                    hasher(&Vec::decode(&mut &object[offset as usize..]).ok()?)
                };

                Some(CallObject { key, offset })
            }
        }
    }
}

/// Metadata of a feed object as raw bytes.
pub type FeedMetadata = Vec<u8>;

/// # Feed Processor
/// Feed Processor defines some useful abstractions that are used during the life cycle of the Feed and its objects.
/// We can provide some custom logic specific to the Feed by implementing a Custom Feed Processor.
///
/// ## Feed Metadata
///
/// Before an object is added to Subspace storage, `put` on Feed processor to give the impl an opportunity to run the object
/// through their custom logic and returns some metadata about the object. Metadata is then stored in the runtime overwriting
/// any metadata of the previous object. The default implementation of Feed processor gives empty metadata about the object.
///
/// ## Feed object mapping
/// Feed indexes the objects in the DSN using offsets within the Block the object is present in. `object_mappings` is the
/// only that must be implemented by the Feed processor. Since DSN is a key value store, there are two different ways keys
/// are derived for given data at the offset within the block
/// - Key derived from content. Feeds use BLAKE2b-256 to derive the key for the data at the offset.
/// - Key provided by the feed processor. Feed processor implementations can instead provide a key for object at the offset.
///
/// ## Examples
/// ### Content based addressing with default hasher
/// ```rust
/// use pallet_feeds::feed_processor::{FeedProcessor, FeedObjectMapping};
/// struct IPFSLike;
/// impl<FeedId> FeedProcessor<FeedId> for IPFSLike { ///
///     fn object_mappings(&self, _feed_id: FeedId, _object: &[u8]) -> Vec<FeedObjectMapping> {
///         vec![FeedObjectMapping::Content { offset: 0 }]
///     }
/// }
/// ```
/// This implements a Content addressable Feed using default Hasher. The entire object is treated as data and hence the offset is zero.
///
/// ### Content based addressing using custom Hasher
/// ```rust
/// use sp_runtime::traits::{BlakeTwo256, Hash};
/// use pallet_feeds::feed_processor::{FeedProcessor, FeedObjectMapping};
/// struct IPFSLike;
/// impl<FeedId> FeedProcessor<FeedId> for IPFSLike { ///
///     fn object_mappings(&self, _feed_id: FeedId, object: &[u8]) -> Vec<FeedObjectMapping> {
///         vec![FeedObjectMapping::Custom { key: BlakeTwo256::hash(object).as_bytes().to_vec(), offset: 0 }]
///     }
/// }
/// ```
/// This implements a Content addressable Feed using BlakeTwo256 hasher. The entire object is treated as data and hence the offset is zero.
pub trait FeedProcessor<FeedId> {
    /// Initiates a specific Feed with data transparent to FeedProcessor
    /// Can be called when re-initializing the feed.
    fn init(&self, _feed_id: FeedId, _data: &[u8]) -> DispatchResult {
        Ok(())
    }

    /// Puts a feed and returns the Metadata if any.
    /// This is called once per extrinsic that puts a feed into a given feed stream.
    fn put(&self, _feed_id: FeedId, _object: &[u8]) -> Result<Option<FeedMetadata>, DispatchError> {
        Ok(None)
    }

    /// Returns any object mappings inside the given object
    fn object_mappings(&self, _feed_id: FeedId, object: &[u8]) -> Vec<FeedObjectMapping>;

    /// Signals a delete to any underlying feed data.
    fn delete(&self, _feed_id: FeedId) -> DispatchResult {
        Ok(())
    }
}

/// Content addressable feed processor impl
/// Offsets the whole object as content thereby signalling to derive `key = hash(object)`
/// Put do not provide any metadata.
impl<FeedId> FeedProcessor<FeedId> for () {
    /// Maps the entire object as content.
    fn object_mappings(&self, _feed_id: FeedId, _object: &[u8]) -> Vec<FeedObjectMapping> {
        vec![FeedObjectMapping::Content { offset: 0 }]
    }
}