• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacks-network / stacks-core / 26250451051-1

21 May 2026 08:11PM UTC coverage: 85.585% (-0.1%) from 85.712%
26250451051-1

Pull #7215

github

ec9d4c
web-flow
Merge 9487bf852 into af1280aac
Pull Request #7215: Chore: fix flake in non_blocking_minority_configured_to_favour_...

188844 of 220651 relevant lines covered (85.58%)

18975267.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.37
/clarity/src/vm/database/key_value_wrapper.rs
1
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2
// Copyright (C) 2020-2026 Stacks Open Internet Foundation
3
//
4
// This program is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// This program is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

17
use std::collections::HashMap;
18
use std::hash::Hash;
19

20
use stacks_common::types::StacksEpochId;
21
use stacks_common::types::chainstate::{StacksBlockId, TrieHash};
22
use stacks_common::util::hash::Sha512Trunc256Sum;
23

24
use super::clarity_store::SpecialCaseHandler;
25
use super::{ClarityBackingStore, ClarityDeserializable};
26
use crate::vm::Value;
27
use crate::vm::database::clarity_store::{ContractCommitment, make_contract_hash_key};
28
use crate::vm::errors::{VmExecutionError, VmInternalError};
29
use crate::vm::types::serialization::SerializationError;
30
use crate::vm::types::{QualifiedContractIdentifier, TypeSignature};
31

32
#[cfg(feature = "rollback_value_check")]
33
type RollbackValueCheck = String;
34
#[cfg(not(feature = "rollback_value_check"))]
35
type RollbackValueCheck = ();
36

37
#[cfg(not(feature = "rollback_value_check"))]
38
fn rollback_value_check(_value: &str, _check: &RollbackValueCheck) {}
39,396,453✔
39

40
#[cfg(not(feature = "rollback_value_check"))]
41
fn rollback_edits_push<T>(edits: &mut Vec<(T, RollbackValueCheck)>, key: T, _value: &str) {
39,432,496✔
42
    edits.push((key, ()));
39,432,496✔
43
}
39,432,496✔
44
// this function is used to check the lookup map when committing at the "bottom" of the
45
//   wrapper -- i.e., when committing to the underlying store. for the _unchecked_ implementation
46
//   this is used to get the edit _value_ out of the lookupmap, for used in the subsequent `put_all`
47
//   command.
48
#[cfg(not(feature = "rollback_value_check"))]
49
fn rollback_check_pre_bottom_commit<T>(
61,099,244✔
50
    edits: Vec<(T, RollbackValueCheck)>,
61,099,244✔
51
    lookup_map: &mut HashMap<T, Vec<String>>,
61,099,244✔
52
) -> Result<Vec<(T, String)>, VmInternalError>
61,099,244✔
53
where
61,099,244✔
54
    T: Eq + Hash + Clone,
61,099,244✔
55
{
56
    for (_, edit_history) in lookup_map.iter_mut() {
64,572,387✔
57
        edit_history.reverse();
34,335,491✔
58
    }
34,335,491✔
59

60
    let output = edits
61,099,244✔
61
        .into_iter()
61,099,244✔
62
        .map(|(key, _)| {
68,200,792✔
63
            let value = rollback_lookup_map(&key, &(), lookup_map)?;
39,034,675✔
64
            Ok((key, value))
39,034,675✔
65
        })
39,034,675✔
66
        .collect();
61,099,244✔
67

68
    assert!(lookup_map.is_empty());
61,099,244✔
69
    output
61,099,244✔
70
}
61,099,244✔
71

72
#[cfg(feature = "rollback_value_check")]
73
fn rollback_value_check(value: &String, check: &RollbackValueCheck) {
74
    assert_eq!(value, check)
75
}
76
#[cfg(feature = "rollback_value_check")]
77
fn rollback_edits_push<T>(edits: &mut Vec<(T, RollbackValueCheck)>, key: T, value: &str)
78
where
79
    T: Eq + Hash + Clone,
80
{
81
    edits.push((key, value.to_owned()));
82
}
83
// this function is used to check the lookup map when committing at the "bottom" of the
84
//   wrapper -- i.e., when committing to the underlying store.
85
#[cfg(feature = "rollback_value_check")]
86
fn rollback_check_pre_bottom_commit<T>(
87
    edits: Vec<(T, RollbackValueCheck)>,
88
    lookup_map: &mut HashMap<T, Vec<String>>,
89
) -> Result<Vec<(T, String)>, VmInternalError>
90
where
91
    T: Eq + Hash + Clone,
92
{
93
    for (_, edit_history) in lookup_map.iter_mut() {
94
        edit_history.reverse();
95
    }
96
    for (key, value) in edits.iter() {
97
        let _ = rollback_lookup_map(key, value, lookup_map);
98
    }
99
    assert!(lookup_map.is_empty());
100
    Ok(edits)
101
}
102

103
/// Result structure for fetched values from the
104
///  underlying store.
105
#[derive(Debug)]
106
pub struct ValueResult {
107
    pub value: Value,
108
    pub serialized_byte_len: u64,
109
}
110

111
pub struct RollbackContext {
112
    edits: Vec<(String, RollbackValueCheck)>,
113
    metadata_edits: Vec<((QualifiedContractIdentifier, String), RollbackValueCheck)>,
114
}
115

116
pub struct RollbackWrapper<'a> {
117
    // the underlying key-value storage.
118
    store: &'a mut dyn ClarityBackingStore,
119
    // lookup_map is a history of edits for a given key.
120
    //   in order of least-recent to most-recent at the tail.
121
    //   this allows ~ O(1) lookups, and ~ O(1) commits, roll-backs (amortized by # of PUTs).
122
    lookup_map: HashMap<String, Vec<String>>,
123
    metadata_lookup_map: HashMap<(QualifiedContractIdentifier, String), Vec<String>>,
124
    // stack keeps track of the most recent rollback context, which tells us which
125
    //   edits were performed by which context. at the moment, each context's edit history
126
    //   is a separate Vec which must be drained into the parent on commits, meaning that
127
    //   the amortized cost of committing a value isn't O(1), but actually O(k) where k is
128
    //   stack depth.
129
    //  TODO: The solution to this is to just have a _single_ edit stack, and merely store indexes
130
    //   to indicate a given contexts "start depth".
131
    stack: Vec<RollbackContext>,
132
    query_pending_data: bool,
133
}
134

135
// This is used for preserving rollback data longer
136
//   than a BackingStore pointer. This is useful to prevent
137
//   a real mess of lifetime parameters in the database/context
138
//   and eval code.
139
pub struct RollbackWrapperPersistedLog {
140
    lookup_map: HashMap<String, Vec<String>>,
141
    metadata_lookup_map: HashMap<(QualifiedContractIdentifier, String), Vec<String>>,
142
    stack: Vec<RollbackContext>,
143
}
144

145
impl From<RollbackWrapper<'_>> for RollbackWrapperPersistedLog {
146
    fn from(o: RollbackWrapper<'_>) -> RollbackWrapperPersistedLog {
31,963,608✔
147
        RollbackWrapperPersistedLog {
31,963,608✔
148
            lookup_map: o.lookup_map,
31,963,608✔
149
            metadata_lookup_map: o.metadata_lookup_map,
31,963,608✔
150
            stack: o.stack,
31,963,608✔
151
        }
31,963,608✔
152
    }
31,963,608✔
153
}
154

155
impl Default for RollbackWrapperPersistedLog {
156
    fn default() -> Self {
×
157
        Self::new()
×
158
    }
×
159
}
160

161
impl RollbackWrapperPersistedLog {
162
    pub fn new() -> RollbackWrapperPersistedLog {
14,506,752✔
163
        RollbackWrapperPersistedLog {
14,506,752✔
164
            lookup_map: HashMap::new(),
14,506,752✔
165
            metadata_lookup_map: HashMap::new(),
14,506,752✔
166
            stack: Vec::new(),
14,506,752✔
167
        }
14,506,752✔
168
    }
14,506,752✔
169

170
    pub fn nest(&mut self) {
14,506,752✔
171
        self.stack.push(RollbackContext {
14,506,752✔
172
            edits: Vec::new(),
14,506,752✔
173
            metadata_edits: Vec::new(),
14,506,752✔
174
        });
14,506,752✔
175
    }
14,506,752✔
176
}
177

178
fn rollback_lookup_map<T>(
39,396,453✔
179
    key: &T,
39,396,453✔
180
    value: &RollbackValueCheck,
39,396,453✔
181
    lookup_map: &mut HashMap<T, Vec<String>>,
39,396,453✔
182
) -> Result<String, VmInternalError>
39,396,453✔
183
where
39,396,453✔
184
    T: Eq + Hash + Clone,
39,396,453✔
185
{
186
    let popped_value;
187
    let remove_edit_deque = {
39,396,453✔
188
        let key_edit_history = lookup_map.get_mut(key).ok_or_else(|| {
39,396,453✔
189
            VmInternalError::Expect(
×
190
                "ERROR: Clarity VM had edit log entry, but not lookup_map entry".into(),
×
191
            )
×
192
        })?;
×
193
        popped_value = key_edit_history.pop().ok_or_else(|| {
39,396,453✔
194
            VmInternalError::Expect("ERROR: expected value in edit history".into())
×
195
        })?;
×
196
        rollback_value_check(&popped_value, value);
39,396,453✔
197
        key_edit_history.is_empty()
39,396,453✔
198
    };
199
    if remove_edit_deque {
39,396,453✔
200
        lookup_map.remove(key);
34,611,604✔
201
    }
34,611,604✔
202
    Ok(popped_value)
39,396,453✔
203
}
39,396,453✔
204

205
impl<'a> RollbackWrapper<'a> {
206
    pub fn new(store: &'a mut dyn ClarityBackingStore) -> RollbackWrapper<'a> {
47,035,487✔
207
        RollbackWrapper {
47,035,487✔
208
            store,
47,035,487✔
209
            lookup_map: HashMap::new(),
47,035,487✔
210
            metadata_lookup_map: HashMap::new(),
47,035,487✔
211
            stack: Vec::new(),
47,035,487✔
212
            query_pending_data: true,
47,035,487✔
213
        }
47,035,487✔
214
    }
47,035,487✔
215

216
    pub fn from_persisted_log(
42,838,479✔
217
        store: &'a mut dyn ClarityBackingStore,
42,838,479✔
218
        log: RollbackWrapperPersistedLog,
42,838,479✔
219
    ) -> RollbackWrapper<'a> {
42,838,479✔
220
        RollbackWrapper {
42,838,479✔
221
            store,
42,838,479✔
222
            lookup_map: log.lookup_map,
42,838,479✔
223
            metadata_lookup_map: log.metadata_lookup_map,
42,838,479✔
224
            stack: log.stack,
42,838,479✔
225
            query_pending_data: true,
42,838,479✔
226
        }
42,838,479✔
227
    }
42,838,479✔
228

229
    pub fn get_cc_special_cases_handler(&self) -> Option<SpecialCaseHandler> {
11,490,546✔
230
        self.store.get_cc_special_cases_handler()
11,490,546✔
231
    }
11,490,546✔
232

233
    pub fn nest(&mut self) {
184,848,104✔
234
        self.stack.push(RollbackContext {
184,848,104✔
235
            edits: Vec::new(),
184,848,104✔
236
            metadata_edits: Vec::new(),
184,848,104✔
237
        });
184,848,104✔
238
    }
184,848,104✔
239

240
    // Rollback the child's edits.
241
    //   this clears all edits from the child's edit queue,
242
    //     and removes any of those edits from the lookup map.
243
    pub fn rollback(&mut self) -> Result<(), VmInternalError> {
102,272,028✔
244
        let mut last_item = self.stack.pop().ok_or_else(|| {
102,272,028✔
245
            VmInternalError::Expect("ERROR: Clarity VM attempted to commit past the stack.".into())
×
246
        })?;
×
247

248
        last_item.edits.reverse();
102,272,028✔
249
        last_item.metadata_edits.reverse();
102,272,028✔
250

251
        for (key, value) in last_item.edits.drain(..) {
102,485,676✔
252
            rollback_lookup_map(&key, &value, &mut self.lookup_map)?;
302,230✔
253
        }
254

255
        for (key, value) in last_item.metadata_edits.drain(..) {
102,272,028✔
256
            rollback_lookup_map(&key, &value, &mut self.metadata_lookup_map)?;
59,548✔
257
        }
258

259
        Ok(())
102,272,028✔
260
    }
102,272,028✔
261

262
    pub fn depth(&self) -> usize {
16,246,284✔
263
        self.stack.len()
16,246,284✔
264
    }
16,246,284✔
265

266
    pub fn commit(&mut self) -> Result<(), VmInternalError> {
97,073,534✔
267
        let mut last_item = self.stack.pop().ok_or_else(|| {
97,073,534✔
268
            VmInternalError::Expect("ERROR: Clarity VM attempted to commit past the stack.".into())
×
269
        })?;
×
270

271
        if let Some(next_up) = self.stack.last_mut() {
97,073,534✔
272
            // bubble up to the next item in the stack
273
            // last_mut() must exist because of the if-statement
274
            for (key, value) in last_item.edits.drain(..) {
81,376,972✔
275
                next_up.edits.push((key, value));
64,528,650✔
276
            }
64,528,650✔
277
            for (key, value) in last_item.metadata_edits.drain(..) {
66,523,912✔
278
                next_up.metadata_edits.push((key, value));
19,376,867✔
279
            }
19,376,867✔
280
        } else {
281
            // stack is empty, committing to the backing store
282
            let all_edits =
30,549,622✔
283
                rollback_check_pre_bottom_commit(last_item.edits, &mut self.lookup_map)?;
30,549,622✔
284
            if !all_edits.is_empty() {
30,549,622✔
285
                self.store.put_all_data(all_edits).map_err(|e| {
11,659,259✔
286
                    VmInternalError::Expect(format!(
×
287
                        "ERROR: Failed to commit data to sql store: {e:?}"
×
288
                    ))
×
289
                })?;
×
290
            }
18,890,363✔
291

292
            let metadata_edits = rollback_check_pre_bottom_commit(
30,549,622✔
293
                last_item.metadata_edits,
30,549,622✔
294
                &mut self.metadata_lookup_map,
30,549,622✔
295
            )?;
×
296
            if !metadata_edits.is_empty() {
30,549,622✔
297
                self.store.put_all_metadata(metadata_edits).map_err(|e| {
940,848✔
298
                    VmInternalError::Expect(format!(
×
299
                        "ERROR: Failed to commit data to sql store: {e:?}"
×
300
                    ))
×
301
                })?;
×
302
            }
29,608,774✔
303
        }
304

305
        Ok(())
97,073,534✔
306
    }
97,073,534✔
307
}
308

309
fn inner_put_data<T>(
39,432,496✔
310
    lookup_map: &mut HashMap<T, Vec<String>>,
39,432,496✔
311
    edits: &mut Vec<(T, RollbackValueCheck)>,
39,432,496✔
312
    key: T,
39,432,496✔
313
    value: String,
39,432,496✔
314
) where
39,432,496✔
315
    T: Eq + Hash + Clone,
39,432,496✔
316
{
317
    let key_edit_deque = lookup_map.entry(key.clone()).or_default();
39,432,496✔
318
    rollback_edits_push(edits, key, &value);
39,432,496✔
319
    key_edit_deque.push(value);
39,432,496✔
320
}
39,432,496✔
321

322
impl RollbackWrapper<'_> {
323
    pub fn put_data(&mut self, key: &str, value: &str) -> Result<(), VmExecutionError> {
32,217,139✔
324
        let current = self.stack.last_mut().ok_or_else(|| {
32,217,139✔
325
            VmInternalError::Expect("ERROR: Clarity VM attempted PUT on non-nested context.".into())
×
326
        })?;
×
327

328
        inner_put_data(
32,217,139✔
329
            &mut self.lookup_map,
32,217,139✔
330
            &mut current.edits,
32,217,139✔
331
            key.to_string(),
32,217,139✔
332
            value.to_string(),
32,217,139✔
333
        );
334
        Ok(())
32,217,139✔
335
    }
32,217,139✔
336

337
    /// Returns whether or not the wrapper is currently retargeted to another block by e.g. an
338
    /// `at-block` scope.
339
    pub fn is_retargeted(&self) -> bool {
62,539,414✔
340
        !self.query_pending_data
62,539,414✔
341
    }
62,539,414✔
342

343
    /// `query_pending_data` indicates whether the rollback wrapper should query the rollback
344
    ///    wrapper's pending data on reads. This is set to `false` during (at-block ...) closures,
345
    ///    and `true` otherwise.
346
    ///
347
    pub fn set_block_hash(
11,339✔
348
        &mut self,
11,339✔
349
        bhh: StacksBlockId,
11,339✔
350
        query_pending_data: bool,
11,339✔
351
    ) -> Result<StacksBlockId, VmExecutionError> {
11,339✔
352
        self.store.set_block_hash(bhh).inspect(|_| {
11,339✔
353
            // use and_then so that query_pending_data is only set once set_block_hash succeeds
354
            //  this doesn't matter in practice, because a set_block_hash failure always aborts
355
            //  the transaction with a runtime error (destroying its environment), but it's much
356
            //  better practice to do this, especially if the abort behavior changes in the future.
357
            self.query_pending_data = query_pending_data;
10,416✔
358
        })
10,416✔
359
    }
11,339✔
360

361
    /// this function will only return commitment proofs for values _already_ materialized
362
    ///  in the underlying store. otherwise it returns None.
363
    pub fn get_data_with_proof<T>(
160✔
364
        &mut self,
160✔
365
        key: &str,
160✔
366
    ) -> Result<Option<(T, Vec<u8>)>, VmExecutionError>
160✔
367
    where
160✔
368
        T: ClarityDeserializable<T>,
160✔
369
    {
370
        self.store
160✔
371
            .get_data_with_proof(key)?
160✔
372
            .map(|(value, proof)| Ok((T::deserialize(&value)?, proof)))
160✔
373
            .transpose()
160✔
374
    }
160✔
375

376
    /// this function will only return commitment proofs for values _already_ materialized
377
    ///  in the underlying store. otherwise it returns None.
378
    pub fn get_data_with_proof_by_hash<T>(
5✔
379
        &mut self,
5✔
380
        hash: &TrieHash,
5✔
381
    ) -> Result<Option<(T, Vec<u8>)>, VmExecutionError>
5✔
382
    where
5✔
383
        T: ClarityDeserializable<T>,
5✔
384
    {
385
        self.store
5✔
386
            .get_data_with_proof_from_path(hash)?
5✔
387
            .map(|(value, proof)| Ok((T::deserialize(&value)?, proof)))
5✔
388
            .transpose()
5✔
389
    }
5✔
390

391
    pub fn get_data<T>(&mut self, key: &str) -> Result<Option<T>, VmExecutionError>
205,244,528✔
392
    where
205,244,528✔
393
        T: ClarityDeserializable<T>,
205,244,528✔
394
    {
395
        self.stack.last().ok_or_else(|| {
205,244,528✔
396
            VmInternalError::Expect("ERROR: Clarity VM attempted GET on non-nested context.".into())
×
397
        })?;
×
398

399
        if self.query_pending_data
205,244,528✔
400
            && let Some(pending_value) = self.lookup_map.get(key).and_then(|x| x.last())
205,196,036✔
401
        {
402
            // if there's pending data and we're querying pending data, return here
403
            return Some(T::deserialize(pending_value)).transpose();
9,824,861✔
404
        }
195,419,667✔
405
        // otherwise, lookup from store
406
        self.store
195,419,667✔
407
            .get_data(key)?
195,419,667✔
408
            .map(|x| T::deserialize(&x))
195,419,667✔
409
            .transpose()
195,419,667✔
410
    }
205,244,528✔
411

412
    /// DO NOT USE IN CONSENSUS CODE.
413
    ///
414
    /// Load data directly from the underlying store, given its trie hash.  The lookup map will not
415
    /// be used.
416
    ///
417
    /// This should never be called from within the Clarity VM, or via block-processing.  It's only
418
    /// meant to be used by the RPC system.
419
    pub fn get_data_by_hash<T>(&mut self, hash: &TrieHash) -> Result<Option<T>, VmExecutionError>
×
420
    where
×
421
        T: ClarityDeserializable<T>,
×
422
    {
423
        self.store
×
424
            .get_data_from_path(hash)?
×
425
            .map(|x| T::deserialize(&x))
×
426
            .transpose()
×
427
    }
×
428

429
    pub fn deserialize_value(
71,537,394✔
430
        value_hex: &str,
71,537,394✔
431
        expected: &TypeSignature,
71,537,394✔
432
        epoch: &StacksEpochId,
71,537,394✔
433
    ) -> Result<ValueResult, SerializationError> {
71,537,394✔
434
        let serialized_byte_len = value_hex.len() as u64 / 2;
71,537,394✔
435
        let sanitize = epoch.value_sanitizing();
71,537,394✔
436
        let value = Value::try_deserialize_hex(value_hex, expected, sanitize)?;
71,537,394✔
437

438
        Ok(ValueResult {
71,536,642✔
439
            value,
71,536,642✔
440
            serialized_byte_len,
71,536,642✔
441
        })
71,536,642✔
442
    }
71,537,394✔
443

444
    /// Get a Clarity value from the underlying Clarity KV store.
445
    /// Returns Some if found, with the Clarity Value and the serialized byte length of the value.
446
    pub fn get_value(
78,911,832✔
447
        &mut self,
78,911,832✔
448
        key: &str,
78,911,832✔
449
        expected: &TypeSignature,
78,911,832✔
450
        epoch: &StacksEpochId,
78,911,832✔
451
    ) -> Result<Option<ValueResult>, SerializationError> {
78,911,832✔
452
        self.stack.last().ok_or_else(|| {
78,911,832✔
453
            SerializationError::DeserializationFailure(
×
454
                "ERROR: Clarity VM attempted GET on non-nested context.".into(),
×
455
            )
×
456
        })?;
×
457

458
        if self.query_pending_data
78,911,832✔
459
            && let Some(x) = self.lookup_map.get(key).and_then(|x| x.last())
78,900,912✔
460
        {
461
            return Ok(Some(Self::deserialize_value(x, expected, epoch)?));
5,062,670✔
462
        }
73,849,162✔
463
        let stored_data = self.store.get_data(key).map_err(|_| {
73,849,162✔
464
            SerializationError::DeserializationFailure(
×
465
                "ERROR: Clarity backing store failure".into(),
×
466
            )
×
467
        })?;
×
468
        match stored_data {
73,849,162✔
469
            Some(x) => Ok(Some(Self::deserialize_value(&x, expected, epoch)?)),
66,473,944✔
470
            None => Ok(None),
7,375,218✔
471
        }
472
    }
78,911,832✔
473

474
    /// This is the height we are currently constructing. It comes from the MARF.
475
    pub fn get_current_block_height(&mut self) -> u32 {
19,790,887✔
476
        self.store.get_current_block_height()
19,790,887✔
477
    }
19,790,887✔
478

479
    /// Is None if `block_height` >= the "currently" under construction Stacks block height.
480
    pub fn get_block_header_hash(&mut self, block_height: u32) -> Option<StacksBlockId> {
5,054,410✔
481
        self.store.get_block_at_height(block_height)
5,054,410✔
482
    }
5,054,410✔
483

484
    pub fn get_contract_hash(
32✔
485
        &mut self,
32✔
486
        contract: &QualifiedContractIdentifier,
32✔
487
    ) -> Result<Option<Sha512Trunc256Sum>, VmExecutionError> {
32✔
488
        let key = make_contract_hash_key(contract);
32✔
489
        let s = match self.get_data::<String>(&key)? {
32✔
490
            Some(s) => s,
28✔
491
            None => return Ok(None),
4✔
492
        };
493
        let cc = ContractCommitment::deserialize(&s)?;
28✔
494
        Ok(Some(cc.hash))
28✔
495
    }
32✔
496

497
    pub fn prepare_for_contract_metadata(
948,619✔
498
        &mut self,
948,619✔
499
        contract: &QualifiedContractIdentifier,
948,619✔
500
        content_hash: Sha512Trunc256Sum,
948,619✔
501
    ) -> Result<(), VmExecutionError> {
948,619✔
502
        let key = make_contract_hash_key(contract);
948,619✔
503
        let value = self.store.make_contract_commitment(content_hash);
948,619✔
504
        self.put_data(&key, &value)
948,619✔
505
    }
948,619✔
506

507
    pub fn insert_metadata(
7,215,357✔
508
        &mut self,
7,215,357✔
509
        contract: &QualifiedContractIdentifier,
7,215,357✔
510
        key: &str,
7,215,357✔
511
        value: &str,
7,215,357✔
512
    ) -> Result<(), VmInternalError> {
7,215,357✔
513
        let current = self.stack.last_mut().ok_or_else(|| {
7,215,357✔
514
            VmInternalError::Expect("ERROR: Clarity VM attempted PUT on non-nested context.".into())
×
515
        })?;
×
516

517
        let metadata_key = (contract.clone(), key.to_string());
7,215,357✔
518

519
        inner_put_data(
7,215,357✔
520
            &mut self.metadata_lookup_map,
7,215,357✔
521
            &mut current.metadata_edits,
7,215,357✔
522
            metadata_key,
7,215,357✔
523
            value.to_string(),
7,215,357✔
524
        );
525
        Ok(())
7,215,357✔
526
    }
7,215,357✔
527

528
    // Throws a NoSuchContract error if contract doesn't exist,
529
    //   returns None if there is no such metadata field.
530
    pub fn get_metadata(
121,013,116✔
531
        &mut self,
121,013,116✔
532
        contract: &QualifiedContractIdentifier,
121,013,116✔
533
        key: &str,
121,013,116✔
534
    ) -> Result<Option<String>, VmExecutionError> {
121,013,116✔
535
        self.stack.last().ok_or_else(|| {
121,013,116✔
536
            VmInternalError::Expect("ERROR: Clarity VM attempted GET on non-nested context.".into())
×
537
        })?;
×
538

539
        // This is THEORETICALLY a spurious clone, but it's hard to turn something like
540
        //  (&A, &B) into &(A, B).
541
        let metadata_key = (contract.clone(), key.to_string());
121,013,116✔
542
        let lookup_result = if self.query_pending_data {
121,013,116✔
543
            self.metadata_lookup_map
121,009,288✔
544
                .get(&metadata_key)
121,009,288✔
545
                .and_then(|x| x.last().cloned())
121,009,288✔
546
        } else {
547
            None
3,828✔
548
        };
549

550
        match lookup_result {
121,013,116✔
551
            Some(x) => Ok(Some(x)),
1,163,947✔
552
            None => self.store.get_metadata(contract, key),
119,849,169✔
553
        }
554
    }
121,013,116✔
555

556
    // Throws a NoSuchContract error if contract doesn't exist,
557
    //   returns None if there is no such metadata field.
558
    pub fn get_metadata_manual(
180✔
559
        &mut self,
180✔
560
        at_height: u32,
180✔
561
        contract: &QualifiedContractIdentifier,
180✔
562
        key: &str,
180✔
563
    ) -> Result<Option<String>, VmExecutionError> {
180✔
564
        self.stack.last().ok_or_else(|| {
180✔
565
            VmInternalError::Expect("ERROR: Clarity VM attempted GET on non-nested context.".into())
×
566
        })?;
×
567

568
        // This is THEORETICALLY a spurious clone, but it's hard to turn something like
569
        //  (&A, &B) into &(A, B).
570
        let metadata_key = (contract.clone(), key.to_string());
180✔
571
        let lookup_result = if self.query_pending_data {
180✔
572
            self.metadata_lookup_map
180✔
573
                .get(&metadata_key)
180✔
574
                .and_then(|x| x.last().cloned())
180✔
575
        } else {
576
            None
×
577
        };
578

579
        match lookup_result {
180✔
580
            Some(x) => Ok(Some(x)),
×
581
            None => self.store.get_metadata_manual(at_height, contract, key),
180✔
582
        }
583
    }
180✔
584

585
    pub fn has_entry(&mut self, key: &str) -> Result<bool, VmExecutionError> {
×
586
        self.stack.last().ok_or_else(|| {
×
587
            VmInternalError::Expect("ERROR: Clarity VM attempted GET on non-nested context.".into())
×
588
        })?;
×
589
        if self.query_pending_data && self.lookup_map.contains_key(key) {
×
590
            Ok(true)
×
591
        } else {
592
            self.store.has_entry(key)
×
593
        }
594
    }
×
595

596
    pub fn has_metadata_entry(
8,496,505✔
597
        &mut self,
8,496,505✔
598
        contract: &QualifiedContractIdentifier,
8,496,505✔
599
        key: &str,
8,496,505✔
600
    ) -> bool {
8,496,505✔
601
        matches!(self.get_metadata(contract, key), Ok(Some(_)))
8,496,505✔
602
    }
8,496,505✔
603

604
    /// Returns `true` if any of the given metadata keys for `contract` has an uncommitted edit in
605
    /// the rollback stack (i.e. would be served from pending data rather than the backing store on
606
    /// a `get_metadata` call).
607
    ///
608
    /// Used by caching implementations to avoid caching reads whose metadata could later be rolled
609
    /// back.
610
    pub fn has_pending_metadata(
24,387,998✔
611
        &self,
24,387,998✔
612
        contract: &QualifiedContractIdentifier,
24,387,998✔
613
        keys: &[&str],
24,387,998✔
614
    ) -> bool {
24,387,998✔
615
        // Retargeted wrappers always read from the backing store, so pending metadata is
616
        // irrelevant.
617
        if self.is_retargeted() {
24,387,998✔
618
            return false;
1,176✔
619
        }
24,386,822✔
620

621
        keys.iter().any(|key| {
73,010,420✔
622
            let metadata_key = (contract.clone(), (*key).to_string());
73,010,420✔
623
            self.metadata_lookup_map.contains_key(&metadata_key)
73,010,420✔
624
        })
73,010,420✔
625
    }
24,387,998✔
626
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc