exocore_chain/
operation.rs

1use bytes::Bytes;
2use exocore_core::{
3    cell::{LocalNode, NodeId},
4    framing::{
5        CapnpFrameBuilder, FrameBuilder, FrameReader, MultihashFrame, MultihashFrameBuilder,
6        SizedFrame, SizedFrameBuilder, TypedCapnpFrame,
7    },
8    sec::{hash::Sha3_256, signature::Signature},
9};
10use exocore_protos::{
11    capnp,
12    generated::data_chain_capnp::{block_signature, chain_operation},
13};
14
15use crate::block::Block;
16
17pub type GroupId = u64;
18pub type OperationId = u64;
19
20pub type OperationFrame<I> =
21    TypedCapnpFrame<MultihashFrame<32, Sha3_256, SizedFrame<I>>, chain_operation::Owned>;
22
23pub type OperationFrameBuilder = SizedFrameBuilder<
24    MultihashFrameBuilder<32, Sha3_256, CapnpFrameBuilder<chain_operation::Owned>>,
25>;
26
27/// Wraps an operation that is stored either in the pending store, or in the
28/// the chain.
29pub trait Operation {
30    fn get_operation_reader(&self) -> Result<chain_operation::Reader, Error>;
31
32    fn as_entry_data(&self) -> Result<&[u8], Error> {
33        let frame_reader = self.get_operation_reader()?;
34        match frame_reader.get_operation().which()? {
35            chain_operation::operation::Entry(entry) => Ok(entry?.get_data()?),
36            _ => Err(Error::NotAnEntry),
37        }
38    }
39
40    fn get_type(&self) -> Result<OperationType, Error> {
41        let operation_reader = self.get_operation_reader()?;
42        Ok(match operation_reader.get_operation().which()? {
43            chain_operation::operation::Which::BlockSign(_) => OperationType::BlockSign,
44            chain_operation::operation::Which::BlockPropose(_) => OperationType::BlockPropose,
45            chain_operation::operation::Which::BlockRefuse(_) => OperationType::BlockRefuse,
46            chain_operation::operation::Which::Entry(_) => OperationType::Entry,
47        })
48    }
49
50    fn get_id(&self) -> Result<OperationId, Error> {
51        let operation_reader = self.get_operation_reader()?;
52        Ok(operation_reader.get_operation_id())
53    }
54
55    fn get_group_id(&self) -> Result<OperationId, Error> {
56        let operation_reader = self.get_operation_reader()?;
57        Ok(operation_reader.get_group_id())
58    }
59}
60
61/// Types of operations
62#[derive(Copy, Clone, Debug, PartialEq, Eq)]
63pub enum OperationType {
64    Entry,
65    BlockPropose,
66    BlockSign,
67    BlockRefuse,
68}
69
70/// Chain operation frame building helper
71pub struct OperationBuilder {
72    pub operation_id: OperationId,
73    pub frame_builder: CapnpFrameBuilder<chain_operation::Owned>,
74}
75
76impl OperationBuilder {
77    pub fn new_entry(operation_id: OperationId, node_id: &NodeId, data: &[u8]) -> OperationBuilder {
78        let mut frame_builder = CapnpFrameBuilder::new();
79
80        let mut operation_builder: chain_operation::Builder = frame_builder.get_builder();
81        operation_builder.set_operation_id(operation_id);
82        operation_builder.set_group_id(operation_id);
83        operation_builder.set_node_id(node_id.to_string().as_str());
84
85        let inner_operation_builder = operation_builder.init_operation();
86
87        let mut new_entry_builder = inner_operation_builder.init_entry();
88        new_entry_builder.set_data(data);
89
90        OperationBuilder {
91            operation_id,
92            frame_builder,
93        }
94    }
95
96    pub fn new_block_proposal<B: Block>(
97        operation_id: OperationId,
98        node_id: &NodeId,
99        block: &B,
100    ) -> Result<OperationBuilder, Error> {
101        Self::new_block_proposal_from_data(operation_id, node_id, &block.as_data_vec())
102    }
103
104    pub fn new_block_proposal_from_data(
105        operation_id: OperationId,
106        node_id: &NodeId,
107        data: &[u8],
108    ) -> Result<OperationBuilder, Error> {
109        let mut frame_builder = CapnpFrameBuilder::new();
110
111        let mut operation_builder: chain_operation::Builder = frame_builder.get_builder();
112        operation_builder.set_operation_id(operation_id);
113        operation_builder.set_group_id(operation_id);
114        operation_builder.set_node_id(node_id.to_string().as_str());
115
116        let inner_operation_builder = operation_builder.init_operation();
117        let mut new_block_builder = inner_operation_builder.init_block_propose();
118        new_block_builder.set_block(data);
119
120        Ok(OperationBuilder {
121            operation_id,
122            frame_builder,
123        })
124    }
125
126    pub fn new_signature_for_block<I: FrameReader>(
127        group_id: OperationId,
128        operation_id: OperationId,
129        node_id: &NodeId,
130        _header: &crate::block::BlockHeaderFrame<I>,
131    ) -> Result<OperationBuilder, Error> {
132        let mut frame_builder = CapnpFrameBuilder::new();
133
134        let mut operation_builder: chain_operation::Builder = frame_builder.get_builder();
135        operation_builder.set_operation_id(operation_id);
136        operation_builder.set_group_id(group_id);
137        operation_builder.set_node_id(node_id.to_string().as_str());
138
139        let inner_operation_builder = operation_builder.init_operation();
140        let new_sig_builder = inner_operation_builder.init_block_sign();
141
142        // TODO: Signature ticket: https://github.com/appaquet/exocore/issues/46
143        //       Create signature for real
144        let signature = Signature::empty();
145
146        let mut sig_builder: block_signature::Builder = new_sig_builder.init_signature();
147        sig_builder.set_node_id(node_id.to_string().as_str());
148        sig_builder.set_node_signature(signature.get_bytes());
149
150        Ok(OperationBuilder {
151            operation_id,
152            frame_builder,
153        })
154    }
155
156    pub fn new_refusal(
157        group_id: OperationId,
158        operation_id: OperationId,
159        node_id: &NodeId,
160    ) -> Result<OperationBuilder, Error> {
161        let mut frame_builder = CapnpFrameBuilder::new();
162
163        let mut operation_builder: chain_operation::Builder = frame_builder.get_builder();
164        operation_builder.set_operation_id(operation_id);
165        operation_builder.set_group_id(group_id);
166        operation_builder.set_node_id(node_id.to_string().as_str());
167
168        let inner_operation_builder = operation_builder.init_operation();
169        let _new_refuse_builder = inner_operation_builder.init_block_refuse();
170
171        Ok(OperationBuilder {
172            operation_id,
173            frame_builder,
174        })
175    }
176
177    pub fn sign_and_build(self, _local_node: &LocalNode) -> Result<NewOperation, Error> {
178        // TODO: Signature ticket: https://github.com/appaquet/exocore/issues/46
179        //       Include signature, not just hash.
180        let msg_frame = self.frame_builder.as_bytes();
181        let signed_frame_builder = MultihashFrameBuilder::<32, Sha3_256, _>::new(msg_frame);
182        let sized_frame_builder = SizedFrameBuilder::new(signed_frame_builder);
183        let final_frame = read_operation_frame(sized_frame_builder.as_bytes())?;
184
185        Ok(NewOperation::from_frame(self.operation_id, final_frame))
186    }
187}
188
189pub fn read_operation_frame<I: FrameReader>(inner: I) -> Result<OperationFrame<I>, Error> {
190    let sized_frame = SizedFrame::new(inner)?;
191    let multihash_frame = MultihashFrame::<32, Sha3_256, _>::new(sized_frame)?;
192    let frame = TypedCapnpFrame::new(multihash_frame)?;
193    Ok(frame)
194}
195
196/// Operation to be added or replaced in the store
197#[derive(Clone)]
198pub struct NewOperation {
199    pub operation_id: OperationId,
200    pub frame: OperationFrame<Bytes>,
201}
202
203impl NewOperation {
204    pub fn from_frame(operation_id: OperationId, frame: OperationFrame<Bytes>) -> NewOperation {
205        NewOperation {
206            operation_id,
207            frame,
208        }
209    }
210}
211
212impl crate::operation::Operation for NewOperation {
213    fn get_operation_reader(&self) -> Result<chain_operation::Reader, Error> {
214        Ok(self.frame.get_reader()?)
215    }
216}
217
218/// Operations related error
219#[derive(Debug, thiserror::Error)]
220pub enum Error {
221    #[error("The operation is not any entry operation")]
222    NotAnEntry,
223
224    #[error("Framing error: {0}")]
225    Framing(#[from] exocore_core::framing::Error),
226
227    #[error("Error in capnp serialization: {0}")]
228    Serialization(#[from] capnp::Error),
229
230    #[error("Field is not in capnp schema: code={0}")]
231    SerializationNotInSchema(u16),
232
233    #[error("Other operation error: {0}")]
234    Other(String),
235}
236
237impl From<capnp::NotInSchema> for Error {
238    fn from(err: capnp::NotInSchema) -> Self {
239        Error::SerializationNotInSchema(err.0)
240    }
241}