oasis_core_runtime/transaction/
tree.rs1use anyhow::{anyhow, Result};
3
4use super::tags::Tags;
5use crate::{
6 common::{crypto::hash::Hash, key_format::KeyFormat},
7 storage::mkvs::{self, sync::ReadSync, Root, WriteLog},
8};
9
10#[derive(Debug)]
13#[repr(u8)]
14enum ArtifactKind {
15 Input = 1,
16 Output = 2,
17}
18
19const ARTIFACT_KIND_INPUT: u8 = ArtifactKind::Input as u8;
22const ARTIFACT_KIND_OUTPUT: u8 = ArtifactKind::Output as u8;
23
24#[derive(Debug)]
26struct TxnKeyFormat {
27 tx_hash: Hash,
29 kind: ArtifactKind,
31}
32
33impl KeyFormat for TxnKeyFormat {
34 fn prefix() -> u8 {
35 b'T'
36 }
37
38 fn size() -> usize {
39 32 + 1
40 }
41
42 fn encode_atoms(self, atoms: &mut Vec<Vec<u8>>) {
43 atoms.push(self.tx_hash.as_ref().to_vec());
44 match self.kind {
45 ArtifactKind::Input => atoms.push(vec![ARTIFACT_KIND_INPUT]),
46 ArtifactKind::Output => atoms.push(vec![ARTIFACT_KIND_OUTPUT]),
47 }
48 }
49
50 fn decode_atoms(data: &[u8]) -> Self {
51 Self {
52 tx_hash: data[..32].into(),
53 kind: match data[32] {
54 ARTIFACT_KIND_INPUT => ArtifactKind::Input,
55 ARTIFACT_KIND_OUTPUT => ArtifactKind::Output,
56 other => panic!("transaction: malformed artifact kind ({:?})", other),
57 },
58 }
59 }
60}
61
62#[derive(Debug, Default)]
67struct TagKeyFormat {
68 key: Vec<u8>,
70 tx_hash: Hash,
72}
73
74pub const TAG_BLOCK_TX_HASH: Hash = Hash([0u8; 32]);
76
77impl KeyFormat for TagKeyFormat {
78 fn prefix() -> u8 {
79 b'E'
80 }
81
82 fn size() -> usize {
83 32
84 }
85
86 fn encode_atoms(self, atoms: &mut Vec<Vec<u8>>) {
87 atoms.push(self.key);
88 atoms.push(self.tx_hash.as_ref().to_vec());
89 }
90
91 fn decode_atoms(data: &[u8]) -> Self {
92 let offset = data.len() - Self::size();
93 let key = data[0..offset].to_vec();
94 let tx_hash = data[offset..].into();
95
96 Self { key, tx_hash }
97 }
98}
99
100#[derive(Clone, Debug, Default, PartialEq, cbor::Encode, cbor::Decode)]
104#[cbor(as_array)]
105struct InputArtifacts {
106 pub input: Vec<u8>,
108 pub batch_order: u32,
114}
115
116#[derive(Clone, Debug, Default, PartialEq, cbor::Encode, cbor::Decode)]
120#[cbor(as_array)]
121struct OutputArtifacts {
122 pub output: Vec<u8>,
124}
125
126pub struct Tree {
128 io_root: Root,
129 tree: mkvs::OverlayTree<mkvs::Tree>,
130}
131
132impl Tree {
133 pub fn new(read_syncer: Box<dyn ReadSync>, io_root: Root) -> Self {
135 Self {
136 io_root,
137 tree: mkvs::OverlayTree::new(
138 mkvs::Tree::builder().with_root(io_root).build(read_syncer),
139 ),
140 }
141 }
142
143 pub fn add_input(&mut self, input: Vec<u8>, batch_order: u32) -> Result<()> {
145 if input.is_empty() {
146 return Err(anyhow!("transaction: no input given"));
147 }
148
149 let tx_hash = Hash::digest_bytes(&input);
150
151 self.tree.insert(
152 &TxnKeyFormat {
153 tx_hash,
154 kind: ArtifactKind::Input,
155 }
156 .encode(),
157 &cbor::to_vec(InputArtifacts { input, batch_order }),
158 )?;
159
160 Ok(())
161 }
162
163 pub fn add_output(&mut self, tx_hash: Hash, output: Vec<u8>, tags: Tags) -> Result<()> {
165 self.tree.insert(
166 &TxnKeyFormat {
167 tx_hash,
168 kind: ArtifactKind::Output,
169 }
170 .encode(),
171 &cbor::to_vec(OutputArtifacts { output }),
172 )?;
173
174 for tag in tags {
176 self.tree.insert(
177 &TagKeyFormat {
178 key: tag.key,
179 tx_hash,
180 }
181 .encode(),
182 &tag.value,
183 )?;
184 }
185
186 Ok(())
187 }
188
189 pub fn add_block_tags(&mut self, tags: Tags) -> Result<()> {
191 for tag in tags {
192 self.tree.insert(
193 &TagKeyFormat {
194 key: tag.key,
195 tx_hash: TAG_BLOCK_TX_HASH,
196 }
197 .encode(),
198 &tag.value,
199 )?;
200 }
201
202 Ok(())
203 }
204
205 pub fn commit(&mut self) -> Result<(WriteLog, Hash)> {
208 self.tree
209 .commit_both(self.io_root.namespace, self.io_root.version)
210 }
211
212 pub fn get_input(&self, tx_hash: Hash) -> Result<Option<Vec<u8>>> {
214 let raw = self.tree.get(
215 &TxnKeyFormat {
216 tx_hash,
217 kind: ArtifactKind::Input,
218 }
219 .encode(),
220 )?;
221 match raw {
222 Some(raw) => {
223 let ia: InputArtifacts = cbor::from_slice(&raw)?;
224 Ok(Some(ia.input))
225 }
226 None => Ok(None),
227 }
228 }
229
230 pub fn get_output(&self, tx_hash: Hash) -> Result<Option<Vec<u8>>> {
232 let raw = self.tree.get(
233 &TxnKeyFormat {
234 tx_hash,
235 kind: ArtifactKind::Output,
236 }
237 .encode(),
238 )?;
239 match raw {
240 Some(raw) => {
241 let oa: OutputArtifacts = cbor::from_slice(&raw)?;
242 Ok(Some(oa.output))
243 }
244 None => Ok(None),
245 }
246 }
247}
248
249#[cfg(test)]
250mod test {
251 use crate::storage::mkvs::sync::*;
252
253 use super::{super::tags::Tag, *};
254
255 #[test]
256 fn test_transaction() {
257 let mut tree = Tree::new(
258 Box::new(NoopReadSyncer),
259 Root {
260 hash: Hash::empty_hash(),
261 ..Default::default()
262 },
263 );
264
265 let input = b"this goes in".to_vec();
266 let tx_hash = Hash::digest_bytes(&input);
267 let orig_tx_hash = tx_hash;
268 tree.add_input(input, 0).unwrap();
269 tree.add_output(
270 tx_hash,
271 b"and this comes out".to_vec(),
272 vec![Tag::new(b"tag1".to_vec(), b"value1".to_vec())],
273 )
274 .unwrap();
275
276 for i in 0..20 {
277 let input = format!("this goes in ({})", i).into_bytes();
278 let tx_hash = Hash::digest_bytes(&input);
279
280 tree.add_input(input, i + 1).unwrap();
281 tree.add_output(
282 tx_hash,
283 b"and this comes out".to_vec(),
284 vec![
285 Tag::new(b"tagA".to_vec(), b"valueA".to_vec()),
286 Tag::new(b"tagB".to_vec(), b"valueB".to_vec()),
287 ],
288 )
289 .unwrap();
290 }
291
292 let (_, root_hash) = tree.commit().unwrap();
294 assert_eq!(
295 format!("{:?}", root_hash),
296 "8399ffa753987b00ec6ab251337c6b88e40812662ed345468fcbf1dbdd16321c",
297 );
298
299 let dec_input = tree.get_input(orig_tx_hash).unwrap();
301 assert_eq!(dec_input, Some(b"this goes in".to_vec()));
302 let dec_output = tree.get_output(orig_tx_hash).unwrap();
303 assert_eq!(dec_output, Some(b"and this comes out".to_vec()));
304
305 let dec_input = tree.get_input(Hash::empty_hash()).unwrap();
306 assert!(dec_input.is_none());
307 let dec_output = tree.get_output(Hash::empty_hash()).unwrap();
308 assert!(dec_output.is_none());
309 }
310}