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
213#[cfg(test)]
214mod test {
215 use crate::storage::mkvs::sync::*;
216
217 use super::{super::tags::Tag, *};
218
219 #[test]
220 fn test_transaction() {
221 let mut tree = Tree::new(
222 Box::new(NoopReadSyncer),
223 Root {
224 hash: Hash::empty_hash(),
225 ..Default::default()
226 },
227 );
228
229 let input = b"this goes in".to_vec();
230 let tx_hash = Hash::digest_bytes(&input);
231 tree.add_input(input, 0).unwrap();
232 tree.add_output(
233 tx_hash,
234 b"and this comes out".to_vec(),
235 vec![Tag::new(b"tag1".to_vec(), b"value1".to_vec())],
236 )
237 .unwrap();
238
239 for i in 0..20 {
240 let input = format!("this goes in ({})", i).into_bytes();
241 let tx_hash = Hash::digest_bytes(&input);
242
243 tree.add_input(input, i + 1).unwrap();
244 tree.add_output(
245 tx_hash,
246 b"and this comes out".to_vec(),
247 vec![
248 Tag::new(b"tagA".to_vec(), b"valueA".to_vec()),
249 Tag::new(b"tagB".to_vec(), b"valueB".to_vec()),
250 ],
251 )
252 .unwrap();
253 }
254
255 let (_, root_hash) = tree.commit().unwrap();
257 assert_eq!(
258 format!("{:?}", root_hash),
259 "8399ffa753987b00ec6ab251337c6b88e40812662ed345468fcbf1dbdd16321c",
260 );
261 }
262}