1use std::{
5 collections::{BTreeMap, BTreeSet},
6 future::Future,
7 mem, vec,
8};
9
10use custom_debug_derive::Debug;
11use linera_base::{
12 data_types::{Blob, BlobContent, Event, OracleResponse, StreamUpdate, Timestamp},
13 ensure,
14 identifiers::{ApplicationId, BlobId, ChainId, StreamId},
15};
16
17use crate::{ExecutionError, OutgoingMessage};
18
19type AppStreamUpdates = BTreeMap<(ChainId, StreamId), (u32, u32)>;
20
21#[derive(Debug, Default)]
24pub struct TransactionTracker {
25 #[debug(skip_if = Option::is_none)]
26 replaying_oracle_responses: Option<vec::IntoIter<OracleResponse>>,
27 #[debug(skip_if = Vec::is_empty)]
28 oracle_responses: Vec<OracleResponse>,
29 #[debug(skip_if = Vec::is_empty)]
30 outgoing_messages: Vec<OutgoingMessage>,
31 local_time: Timestamp,
33 transaction_index: u32,
35 next_application_index: u32,
36 next_chain_index: u32,
37 events: Vec<Event>,
39 blobs: BTreeMap<BlobId, BlobContent>,
49 previously_created_blobs: BTreeMap<BlobId, BlobContent>,
51 operation_result: Option<Vec<u8>>,
53 streams_to_process: BTreeMap<ApplicationId, AppStreamUpdates>,
55 blobs_published: BTreeSet<BlobId>,
57 free_blob_ids: BTreeSet<BlobId>,
59}
60
61#[derive(Debug, Default)]
63pub struct TransactionOutcome {
64 #[debug(skip_if = Vec::is_empty)]
66 pub oracle_responses: Vec<OracleResponse>,
67 #[debug(skip_if = Vec::is_empty)]
69 pub outgoing_messages: Vec<OutgoingMessage>,
70 pub next_application_index: u32,
72 pub next_chain_index: u32,
74 pub events: Vec<Event>,
76 pub blobs: Vec<Blob>,
78 pub operation_result: Vec<u8>,
80 pub blobs_published: BTreeSet<BlobId>,
82 pub free_blob_ids: BTreeSet<BlobId>,
84}
85
86impl TransactionTracker {
87 pub fn new(
89 local_time: Timestamp,
90 transaction_index: u32,
91 next_application_index: u32,
92 next_chain_index: u32,
93 oracle_responses: Option<Vec<OracleResponse>>,
94 blobs: &[Vec<Blob>],
95 ) -> Self {
96 let mut previously_created_blobs = BTreeMap::new();
97 for tx_blobs in blobs {
98 for blob in tx_blobs {
99 previously_created_blobs.insert(blob.id(), blob.content().clone());
100 }
101 }
102 TransactionTracker {
103 local_time,
104 transaction_index,
105 next_application_index,
106 next_chain_index,
107 replaying_oracle_responses: oracle_responses.map(Vec::into_iter),
108 previously_created_blobs,
109 ..Self::default()
110 }
111 }
112
113 pub fn with_blobs(mut self, blobs: BTreeMap<BlobId, BlobContent>) -> Self {
115 self.blobs = blobs;
116 self
117 }
118
119 pub fn local_time(&self) -> Timestamp {
121 self.local_time
122 }
123
124 pub fn set_local_time(&mut self, local_time: Timestamp) {
126 self.local_time = local_time;
127 }
128
129 pub fn transaction_index(&self) -> u32 {
131 self.transaction_index
132 }
133
134 pub fn next_application_index(&mut self) -> u32 {
136 let index = self.next_application_index;
137 self.next_application_index += 1;
138 index
139 }
140
141 pub fn next_chain_index(&mut self) -> u32 {
143 let index = self.next_chain_index;
144 self.next_chain_index += 1;
145 index
146 }
147
148 pub fn add_outgoing_message(&mut self, message: OutgoingMessage) {
150 self.outgoing_messages.push(message);
151 }
152
153 pub fn add_outgoing_messages(&mut self, messages: impl IntoIterator<Item = OutgoingMessage>) {
155 for message in messages {
156 self.add_outgoing_message(message);
157 }
158 }
159
160 pub fn add_event(&mut self, stream_id: StreamId, index: u32, value: Vec<u8>) {
162 self.events.push(Event {
163 stream_id,
164 index,
165 value,
166 });
167 }
168
169 pub fn get_blob_content(&self, blob_id: &BlobId) -> Option<&BlobContent> {
171 if let Some(content) = self.blobs.get(blob_id) {
172 return Some(content);
173 }
174 self.previously_created_blobs.get(blob_id)
175 }
176
177 pub fn add_created_blob(&mut self, blob: Blob) {
179 self.blobs.insert(blob.id(), blob.into_content());
180 }
181
182 pub fn add_published_blob(&mut self, blob_id: BlobId) {
184 self.blobs_published.insert(blob_id);
185 }
186
187 pub fn mark_blob_free(&mut self, blob_id: BlobId) {
189 self.free_blob_ids.insert(blob_id);
190 }
191
192 pub fn created_blobs(&self) -> &BTreeMap<BlobId, BlobContent> {
194 &self.blobs
195 }
196
197 pub fn add_operation_result(&mut self, result: Option<Vec<u8>>) {
199 self.operation_result = result
200 }
201
202 pub async fn oracle<F, G>(&mut self, f: F) -> Result<&OracleResponse, ExecutionError>
206 where
207 F: FnOnce() -> G,
208 G: Future<Output = Result<OracleResponse, ExecutionError>>,
209 {
210 let response = match self.next_replayed_oracle_response()? {
211 Some(response) => response,
212 None => f().await?,
213 };
214 self.oracle_responses.push(response);
215 Ok(self.oracle_responses.last().unwrap())
216 }
217
218 pub fn add_stream_to_process(
220 &mut self,
221 application_id: ApplicationId,
222 chain_id: ChainId,
223 stream_id: StreamId,
224 previous_index: u32,
225 next_index: u32,
226 ) {
227 if next_index == previous_index {
228 return; }
230 self.streams_to_process
231 .entry(application_id)
232 .or_default()
233 .entry((chain_id, stream_id))
234 .and_modify(|(pi, ni)| {
235 *pi = (*pi).min(previous_index);
236 *ni = (*ni).max(next_index);
237 })
238 .or_insert_with(|| (previous_index, next_index));
239 }
240
241 pub fn remove_stream_to_process(
243 &mut self,
244 application_id: ApplicationId,
245 chain_id: ChainId,
246 stream_id: StreamId,
247 ) {
248 let Some(streams) = self.streams_to_process.get_mut(&application_id) else {
249 return;
250 };
251 if streams.remove(&(chain_id, stream_id)).is_some() && streams.is_empty() {
252 self.streams_to_process.remove(&application_id);
253 }
254 }
255
256 pub fn take_streams_to_process(&mut self) -> BTreeMap<ApplicationId, Vec<StreamUpdate>> {
258 mem::take(&mut self.streams_to_process)
259 .into_iter()
260 .map(|(app_id, streams)| {
261 let updates = streams
262 .into_iter()
263 .map(
264 |((chain_id, stream_id), (previous_index, next_index))| StreamUpdate {
265 chain_id,
266 stream_id,
267 previous_index,
268 next_index,
269 },
270 )
271 .collect();
272 (app_id, updates)
273 })
274 .collect()
275 }
276
277 pub fn replay_oracle_response(
280 &mut self,
281 oracle_response: OracleResponse,
282 ) -> Result<bool, ExecutionError> {
283 let replaying = if let Some(recorded_response) = self.next_replayed_oracle_response()? {
284 ensure!(
285 recorded_response == oracle_response,
286 ExecutionError::OracleResponseMismatch
287 );
288 true
289 } else {
290 false
291 };
292 self.oracle_responses.push(oracle_response);
293 Ok(replaying)
294 }
295
296 fn next_replayed_oracle_response(&mut self) -> Result<Option<OracleResponse>, ExecutionError> {
304 let Some(responses) = &mut self.replaying_oracle_responses else {
305 return Ok(None); };
307 let response = responses
308 .next()
309 .ok_or_else(|| ExecutionError::MissingOracleResponse)?;
310 Ok(Some(response))
311 }
312
313 pub fn into_outcome(self) -> Result<TransactionOutcome, ExecutionError> {
315 let TransactionTracker {
316 replaying_oracle_responses,
317 oracle_responses,
318 outgoing_messages,
319 local_time: _,
320 transaction_index: _,
321 next_application_index,
322 next_chain_index,
323 events,
324 blobs,
325 previously_created_blobs: _,
326 operation_result,
327 streams_to_process,
328 blobs_published,
329 free_blob_ids,
330 } = self;
331 ensure!(
332 streams_to_process.is_empty(),
333 ExecutionError::UnprocessedStreams
334 );
335 if let Some(mut responses) = replaying_oracle_responses {
336 ensure!(
337 responses.next().is_none(),
338 ExecutionError::UnexpectedOracleResponse
339 );
340 }
341 let blobs = blobs
342 .into_iter()
343 .map(|(blob_id, content)| Blob::new_with_hash_unchecked(blob_id, content))
344 .collect::<Vec<_>>();
345 Ok(TransactionOutcome {
346 outgoing_messages,
347 oracle_responses,
348 next_application_index,
349 next_chain_index,
350 events,
351 blobs,
352 operation_result: operation_result.unwrap_or_default(),
353 blobs_published,
354 free_blob_ids,
355 })
356 }
357}
358
359#[cfg(with_testing)]
360impl TransactionTracker {
361 pub fn new_replaying(oracle_responses: Vec<OracleResponse>) -> Self {
364 TransactionTracker::new(Timestamp::from(0), 0, 0, 0, Some(oracle_responses), &[])
365 }
366
367 pub fn new_replaying_blobs<T>(blob_ids: T) -> Self
370 where
371 T: IntoIterator,
372 T::Item: std::borrow::Borrow<BlobId>,
373 {
374 use std::borrow::Borrow;
375
376 let oracle_responses = blob_ids
377 .into_iter()
378 .map(|blob_id| OracleResponse::Blob(*blob_id.borrow()))
379 .collect();
380 TransactionTracker::new_replaying(oracle_responses)
381 }
382}