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

tari-project / tari / 24654956413

20 Apr 2026 07:52AM UTC coverage: 61.03% (+0.09%) from 60.945%
24654956413

push

github

web-flow
fix(sidechain)!: include epoch_hash in sidechain block header (#7767)

Description
---
fix(sidechain)!: include epoch_hash in sidechain block header

Motivation and Context
---
We need to read the epoch_hash from the sidechain header on Ootle
(https://github.com/tari-project/tari-ootle/pull/2017#issuecomment-4259006786)

This increases the proof size (over the wire) by 33 bytes

How Has This Been Tested?
---
Existing sidechain tests (fixtures updated)

Breaking Changes
---

- [ ] None
- [ ] Requires data directory on base node to be deleted
- [ ] Requires hard fork
- [x] Other - Please specify

BREAKING CHANGE: this has no breaking change on current testnets or
mainnet. However, it is a breaking change for Ootle when it upgrades to
use these changes.

1 of 3 new or added lines in 2 files covered. (33.33%)

112 existing lines in 9 files now uncovered.

70783 of 115981 relevant lines covered (61.03%)

224161.51 hits per line

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

29.34
/base_layer/wallet/src/storage/database.rs
1
// Copyright 2019. The Tari Project
2
//
3
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4
// following conditions are met:
5
//
6
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7
// disclaimer.
8
//
9
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
10
// following disclaimer in the documentation and/or other materials provided with the distribution.
11
//
12
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
13
// products derived from this software without specific prior written permission.
14
//
15
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
21
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22

23
use std::{
24
    fmt::{Display, Error, Formatter},
25
    sync::Arc,
26
};
27

28
use log::*;
29
use tari_common_types::{chain_metadata::ChainMetadata, seeds::cipher_seed::CipherSeed, types::CompressedCommitment};
30
use tari_comms::{
31
    multiaddr::Multiaddr,
32
    peer_manager::{IdentitySignature, PeerFeatures},
33
};
34
use tari_transaction_key_manager::legacy_key_manager::wallet_types::LegacyWalletType;
35
use tari_utilities::SafePassword;
36

37
use crate::{
38
    error::WalletStorageError,
39
    storage::sqlite_db::models::DbBurnProof,
40
    utxo_scanner_service::service::ScannedBlock,
41
};
42

43
const LOG_TARGET: &str = "wallet::database";
44

45
/// This trait defines the functionality that a database backend need
46
pub trait WalletBackend: Send + Sync + Clone {
47
    /// Retrieve the record associated with the provided DbKey
48
    fn fetch(&self, key: &DbKey) -> Result<Option<DbValue>, WalletStorageError>;
49
    /// Modify the state the of the backend with a write operation
50
    fn write(&self, op: WriteOperation) -> Result<Option<DbValue>, WalletStorageError>;
51

52
    fn get_last_scanned_height(&self) -> Result<Option<u64>, WalletStorageError>;
53
    fn get_scanned_blocks(&self) -> Result<Vec<ScannedBlock>, WalletStorageError>;
54
    fn save_scanned_block(&self, scanned_block: ScannedBlock) -> Result<(), WalletStorageError>;
55
    fn clear_scanned_blocks(&self) -> Result<(), WalletStorageError>;
56
    /// Clear scanned blocks from the givne height and higher
57
    fn clear_scanned_blocks_from_and_higher(&self, height: u64) -> Result<(), WalletStorageError>;
58
    /// Clear scanned block history from before the specified height.
59
    fn clear_scanned_blocks_before_height(&self, height: u64) -> Result<(), WalletStorageError>;
60

61
    /// Apply a sparse storage schedule to scanned blocks, keeping all recent blocks
62
    /// and progressively sparser checkpoints for older blocks. Blocks containing
63
    /// recovered outputs are always preserved.
64
    fn apply_sparse_scanned_blocks_schedule(&self, tip_height: u64) -> Result<(), WalletStorageError>;
65

66
    /// Change the passphrase used to encrypt the database
67
    fn change_passphrase(&self, existing: &SafePassword, new: &SafePassword) -> Result<(), WalletStorageError>;
68

69
    fn fetch_burn_proofs(&self) -> Result<Vec<DbBurnProof>, WalletStorageError>;
70
    fn get_burn_proof_by_commitment(
71
        &self,
72
        commitment: &CompressedCommitment,
73
    ) -> Result<Option<DbBurnProof>, WalletStorageError>;
74
    fn delete_burn_proof(&self, id: i32) -> Result<(), WalletStorageError>;
75
}
76

77
#[derive(Debug, Clone, PartialEq)]
78
pub enum DbKey {
79
    CommsAddress,
80
    CommsFeatures,
81
    CommsIdentitySignature,
82
    BaseNodeChainMetadata,
83
    ClientKey(String),
84
    MasterSeed,
85
    EncryptedMainKey,    // the database encryption key, itself encrypted with the secondary key
86
    SecondaryKeySalt,    // the salt used (with the user's passphrase) to derive the secondary derivation key
87
    SecondaryKeyVersion, // the parameter version for the secondary derivation key
88
    SecondaryKeyHash,    // a hash commitment to the secondary derivation key
89
    WalletBirthday,
90
    LastAccessedNetwork,
91
    LastAccessedVersion,
92
    WalletType,
93
}
94

95
impl DbKey {
96
    pub fn to_key_string(&self) -> String {
122✔
97
        match self {
122✔
98
            DbKey::MasterSeed => "MasterSeed".to_string(),
14✔
99
            DbKey::CommsAddress => "CommsAddress".to_string(),
×
100
            DbKey::CommsFeatures => "NodeFeatures".to_string(),
×
101
            DbKey::ClientKey(k) => format!("ClientKey.{k}"),
×
102
            DbKey::BaseNodeChainMetadata => "BaseNodeChainMetadata".to_string(),
×
103
            DbKey::EncryptedMainKey => "EncryptedMainKey".to_string(),
25✔
104
            DbKey::SecondaryKeySalt => "SecondaryKeySalt".to_string(),
25✔
105
            DbKey::SecondaryKeyVersion => "SecondaryKeyVersion".to_string(),
25✔
106
            DbKey::SecondaryKeyHash => "SecondaryKeyHash".to_string(),
26✔
107
            DbKey::WalletBirthday => "WalletBirthday".to_string(),
7✔
108
            DbKey::CommsIdentitySignature => "CommsIdentitySignature".to_string(),
×
109
            DbKey::LastAccessedNetwork => "LastAccessedNetwork".to_string(),
×
110
            DbKey::LastAccessedVersion => "LastAccessedVersion".to_string(),
×
111
            DbKey::WalletType => "WalletType".to_string(),
×
112
        }
113
    }
122✔
114
}
115

116
pub enum DbValue {
117
    CommsAddress(Multiaddr),
118
    CommsFeatures(PeerFeatures),
119
    CommsIdentitySignature(Box<IdentitySignature>),
120
    ClientValue(String),
121
    ValueCleared,
122
    BaseNodeChainMetadata(ChainMetadata),
123
    MasterSeed(CipherSeed),
124
    EncryptedMainKey(String),
125
    SecondaryKeySalt(String),
126
    SecondaryKeyVersion(String),
127
    SecondaryKeyHash(String),
128
    WalletBirthday(String),
129
    LastAccessedNetwork(String),
130
    LastAccessedVersion(String),
131
    WalletType(Box<LegacyWalletType>),
132
}
133

134
#[derive(Clone)]
135
pub enum DbKeyValuePair {
136
    ClientKeyValue(String, String),
137
    BaseNodeChainMetadata(ChainMetadata),
138
    MasterSeed(CipherSeed),
139
    CommsAddress(Multiaddr),
140
    CommsFeatures(PeerFeatures),
141
    CommsIdentitySignature(Box<IdentitySignature>),
142
    NetworkAndVersion((String, String)),
143
    WalletType(Box<LegacyWalletType>),
144
}
145

146
pub enum WriteOperation {
147
    Insert(DbKeyValuePair),
148
    Remove(DbKey),
149
}
150

151
#[derive(Clone)]
152
pub struct WalletDatabase<T> {
153
    db: Arc<T>,
154
}
155

156
impl<T> WalletDatabase<T>
157
where T: WalletBackend + 'static
158
{
159
    pub fn new(db: T) -> Self {
1✔
160
        Self { db: Arc::new(db) }
1✔
161
    }
1✔
162

163
    pub fn change_passphrase(&self, existing: &SafePassword, new: &SafePassword) -> Result<(), WalletStorageError> {
×
164
        self.db.change_passphrase(existing, new)?;
×
165
        Ok(())
×
166
    }
×
167

168
    pub fn get_master_seed(&self) -> Result<Option<CipherSeed>, WalletStorageError> {
3✔
169
        let c = match self.db.fetch(&DbKey::MasterSeed) {
3✔
170
            Ok(None) => Ok(None),
2✔
171
            Ok(Some(DbValue::MasterSeed(k))) => Ok(Some(k)),
1✔
172
            Ok(Some(other)) => unexpected_result(DbKey::MasterSeed, other),
×
173
            Err(e) => log_error(DbKey::MasterSeed, e),
×
174
        }?;
×
175
        Ok(c)
3✔
176
    }
3✔
177

178
    pub fn set_master_seed(&self, seed: CipherSeed) -> Result<(), WalletStorageError> {
1✔
179
        self.db
1✔
180
            .write(WriteOperation::Insert(DbKeyValuePair::MasterSeed(seed)))?;
1✔
181
        Ok(())
1✔
182
    }
1✔
183

184
    pub fn clear_master_seed(&self) -> Result<(), WalletStorageError> {
1✔
185
        self.db.write(WriteOperation::Remove(DbKey::MasterSeed))?;
1✔
186
        Ok(())
1✔
187
    }
1✔
188

189
    pub fn get_node_address(&self) -> Result<Option<Multiaddr>, WalletStorageError> {
×
190
        let c = match self.db.fetch(&DbKey::CommsAddress) {
×
191
            Ok(None) => Ok(None),
×
192
            Ok(Some(DbValue::CommsAddress(k))) => Ok(Some(k)),
×
193
            Ok(Some(other)) => unexpected_result(DbKey::CommsAddress, other),
×
194
            Err(e) => log_error(DbKey::CommsAddress, e),
×
195
        }?;
×
196
        Ok(c)
×
197
    }
×
198

199
    pub fn set_node_address(&self, address: Multiaddr) -> Result<(), WalletStorageError> {
×
200
        self.db
×
201
            .write(WriteOperation::Insert(DbKeyValuePair::CommsAddress(address)))?;
×
202
        Ok(())
×
203
    }
×
204

205
    pub fn get_node_features(&self) -> Result<Option<PeerFeatures>, WalletStorageError> {
×
206
        let c = match self.db.fetch(&DbKey::CommsFeatures) {
×
207
            Ok(None) => Ok(None),
×
208
            Ok(Some(DbValue::CommsFeatures(k))) => Ok(Some(k)),
×
209
            Ok(Some(other)) => unexpected_result(DbKey::CommsFeatures, other),
×
210
            Err(e) => log_error(DbKey::CommsFeatures, e),
×
211
        }?;
×
212
        Ok(c)
×
213
    }
×
214

215
    pub fn set_node_features(&self, features: PeerFeatures) -> Result<(), WalletStorageError> {
×
216
        self.db
×
217
            .write(WriteOperation::Insert(DbKeyValuePair::CommsFeatures(features)))?;
×
218
        Ok(())
×
219
    }
×
220

221
    pub fn get_comms_identity_signature(&self) -> Result<Option<IdentitySignature>, WalletStorageError> {
×
222
        let sig = match self.db.fetch(&DbKey::CommsIdentitySignature) {
×
223
            Ok(None) => Ok(None),
×
224
            Ok(Some(DbValue::CommsIdentitySignature(k))) => Ok(Some(*k)),
×
225
            Ok(Some(other)) => unexpected_result(DbKey::CommsIdentitySignature, other),
×
226
            Err(e) => log_error(DbKey::CommsIdentitySignature, e),
×
227
        }?;
×
228
        Ok(sig)
×
229
    }
×
230

231
    pub fn set_comms_identity_signature(&self, sig: IdentitySignature) -> Result<(), WalletStorageError> {
×
232
        self.db
×
233
            .write(WriteOperation::Insert(DbKeyValuePair::CommsIdentitySignature(
×
234
                Box::new(sig),
×
235
            )))?;
×
236
        Ok(())
×
237
    }
×
238

239
    pub fn set_client_key_value(&self, key: String, value: String) -> Result<(), WalletStorageError> {
4✔
240
        self.db
4✔
241
            .write(WriteOperation::Insert(DbKeyValuePair::ClientKeyValue(key, value)))?;
4✔
242
        Ok(())
4✔
243
    }
4✔
244

245
    pub fn set_last_network_and_version(&self, network: String, version: String) -> Result<(), WalletStorageError> {
×
246
        self.db
×
247
            .write(WriteOperation::Insert(DbKeyValuePair::NetworkAndVersion((
×
248
                network, version,
×
249
            ))))?;
×
250
        Ok(())
×
251
    }
×
252

253
    pub fn get_client_key_value(&self, key: String) -> Result<Option<String>, WalletStorageError> {
2✔
254
        let c = match self.db.fetch(&DbKey::ClientKey(key.clone())) {
2✔
255
            Ok(None) => Ok(None),
1✔
256
            Ok(Some(DbValue::ClientValue(k))) => Ok(Some(k)),
1✔
257
            Ok(Some(other)) => unexpected_result(DbKey::ClientKey(key), other),
×
258
            Err(e) => log_error(DbKey::ClientKey(key), e),
×
259
        }?;
×
260
        Ok(c)
2✔
261
    }
2✔
262

263
    pub fn get_client_key_from_str<V>(&self, key: String) -> Result<Option<V>, WalletStorageError>
×
264
    where
×
265
        V: std::str::FromStr,
×
266
        V::Err: ToString,
×
267
    {
268
        let value = match self.db.fetch(&DbKey::ClientKey(key.clone())) {
×
269
            Ok(None) => Ok(None),
×
270
            Ok(Some(DbValue::ClientValue(k))) => Ok(Some(k)),
×
271
            Ok(Some(other)) => unexpected_result(DbKey::ClientKey(key), other),
×
272
            Err(e) => log_error(DbKey::ClientKey(key), e),
×
273
        }?;
×
274

275
        match value {
×
276
            Some(c) => {
×
277
                let a = V::from_str(&c).map_err(|err| WalletStorageError::ConversionError(err.to_string()))?;
×
278
                Ok(Some(a))
×
279
            },
280
            None => Ok(None),
×
281
        }
282
    }
×
283

284
    pub fn clear_client_value(&self, key: String) -> Result<bool, WalletStorageError> {
3✔
285
        let c = match self.db.write(WriteOperation::Remove(DbKey::ClientKey(key.clone()))) {
3✔
286
            Ok(None) => Ok(false),
2✔
287
            Ok(Some(DbValue::ValueCleared)) => Ok(true),
1✔
288
            Ok(Some(other)) => unexpected_result(DbKey::ClientKey(key), other),
×
289
            Err(e) => log_error(DbKey::ClientKey(key), e),
×
290
        }?;
×
291
        Ok(c)
3✔
292
    }
3✔
293

294
    pub fn get_wallet_birthday(&self) -> Result<u16, WalletStorageError> {
×
295
        let result = match self.db.fetch(&DbKey::WalletBirthday) {
×
296
            Ok(None) => Err(WalletStorageError::ValueNotFound(DbKey::WalletBirthday)),
×
297
            Ok(Some(DbValue::WalletBirthday(b))) => Ok(b
×
298
                .parse::<u16>()
×
299
                .map_err(|_| WalletStorageError::ConversionError("Could not parse wallet birthday".to_string()))?),
×
300
            Ok(Some(other)) => unexpected_result(DbKey::WalletBirthday, other),
×
301
            Err(e) => log_error(DbKey::WalletBirthday, e),
×
302
        }?;
×
303
        Ok(result)
×
304
    }
×
305

306
    pub fn get_last_scanned_height(&self) -> Result<Option<u64>, WalletStorageError> {
×
307
        let result = self.db.get_last_scanned_height()?;
×
308
        Ok(result)
×
309
    }
×
310

311
    pub fn get_scanned_blocks(&self) -> Result<Vec<ScannedBlock>, WalletStorageError> {
×
312
        let result = self.db.get_scanned_blocks()?;
×
313
        Ok(result)
×
314
    }
×
315

316
    pub fn save_scanned_block(&self, scanned_block: ScannedBlock) -> Result<(), WalletStorageError> {
×
317
        self.db.save_scanned_block(scanned_block)?;
×
318
        Ok(())
×
319
    }
×
320

321
    pub fn clear_scanned_blocks(&self) -> Result<(), WalletStorageError> {
×
322
        self.db.clear_scanned_blocks()?;
×
323
        Ok(())
×
324
    }
×
325

326
    pub fn clear_scanned_blocks_from_and_higher(&self, height: u64) -> Result<(), WalletStorageError> {
×
327
        self.db.clear_scanned_blocks_from_and_higher(height)?;
×
328
        Ok(())
×
329
    }
×
330

331
    pub fn clear_scanned_blocks_before_height(&self, height: u64) -> Result<(), WalletStorageError> {
×
332
        self.db.clear_scanned_blocks_before_height(height)?;
×
333
        Ok(())
×
334
    }
×
335

336
    pub fn apply_sparse_scanned_blocks_schedule(&self, tip_height: u64) -> Result<(), WalletStorageError> {
×
337
        self.db.apply_sparse_scanned_blocks_schedule(tip_height)?;
×
338
        Ok(())
×
UNCOV
339
    }
×
340

341
    pub fn get_all_burn_proofs(&self) -> Result<Vec<DbBurnProof>, WalletStorageError> {
×
342
        self.db.fetch_burn_proofs()
×
UNCOV
343
    }
×
344

345
    pub fn get_burn_proof_by_commitment(
×
346
        &self,
×
347
        commitment: &CompressedCommitment,
×
348
    ) -> Result<Option<DbBurnProof>, WalletStorageError> {
×
349
        self.db.get_burn_proof_by_commitment(commitment)
×
UNCOV
350
    }
×
351

352
    pub fn delete_burn_proof(&self, id: i32) -> Result<(), WalletStorageError> {
×
353
        self.db.delete_burn_proof(id)
×
UNCOV
354
    }
×
355

356
    pub fn get_wallet_type(&self) -> Result<Option<LegacyWalletType>, WalletStorageError> {
×
357
        match self.db.fetch(&DbKey::WalletType) {
×
358
            Ok(None) => Ok(None),
×
359
            Ok(Some(DbValue::WalletType(k))) => Ok(Some(*k)),
×
360
            Ok(Some(other)) => unexpected_result(DbKey::WalletType, other),
×
UNCOV
361
            Err(e) => log_error(DbKey::WalletType, e),
×
362
        }
UNCOV
363
    }
×
364

365
    pub fn set_wallet_type(&self, wallet_type: LegacyWalletType) -> Result<(), WalletStorageError> {
×
366
        self.db
×
367
            .write(WriteOperation::Insert(DbKeyValuePair::WalletType(Box::new(
×
368
                wallet_type,
×
369
            ))))?;
×
370
        Ok(())
×
UNCOV
371
    }
×
372
}
373

374
impl Display for DbValue {
375
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
×
376
        match self {
×
377
            DbValue::MasterSeed(k) => f.write_str(&format!("MasterSeed: {k:?}")),
×
378
            DbValue::ClientValue(v) => f.write_str(&format!("ClientValue: {v:?}")),
×
379
            DbValue::ValueCleared => f.write_str("ValueCleared"),
×
380
            DbValue::CommsFeatures(_) => f.write_str("Node features"),
×
381
            DbValue::CommsAddress(_) => f.write_str("Comms Address"),
×
382
            DbValue::BaseNodeChainMetadata(v) => f.write_str(&format!("Last seen Chain metadata from base node:{v}")),
×
383
            DbValue::EncryptedMainKey(k) => f.write_str(&format!("EncryptedMainKey: {k:?}")),
×
384
            DbValue::SecondaryKeySalt(s) => f.write_str(&format!("SecondaryKeySalt: {s}")),
×
385
            DbValue::SecondaryKeyVersion(v) => f.write_str(&format!("SecondaryKeyVersion: {v}")),
×
386
            DbValue::SecondaryKeyHash(h) => f.write_str(&format!("SecondaryKeyHash: {h}")),
×
387
            DbValue::WalletBirthday(b) => f.write_str(&format!("WalletBirthday: {b}")),
×
388
            DbValue::CommsIdentitySignature(_) => f.write_str("CommsIdentitySignature"),
×
389
            DbValue::LastAccessedNetwork(network) => f.write_str(&format!("LastAccessedNetwork: {network}")),
×
390
            DbValue::LastAccessedVersion(version) => f.write_str(&format!("LastAccessedVersion: {version}")),
×
UNCOV
391
            DbValue::WalletType(wallet_type) => f.write_str(&format!("WalletType: {wallet_type:?}")),
×
392
        }
UNCOV
393
    }
×
394
}
395

396
fn log_error<T>(req: DbKey, err: WalletStorageError) -> Result<T, WalletStorageError> {
×
397
    error!(
×
UNCOV
398
        target: LOG_TARGET,
×
399
        "Database access error on request: {}: {}",
UNCOV
400
        req.to_key_string(),
×
401
        err
402
    );
403
    Err(err)
×
UNCOV
404
}
×
405

406
fn unexpected_result<T>(req: DbKey, res: DbValue) -> Result<T, WalletStorageError> {
×
UNCOV
407
    let msg = format!(
×
408
        "Unexpected result for database query {}. Response: {}",
UNCOV
409
        req.to_key_string(),
×
410
        res
411
    );
412
    error!(target: LOG_TARGET, "{msg}");
×
413
    Err(WalletStorageError::UnexpectedResult(msg))
×
UNCOV
414
}
×
415

416
#[cfg(test)]
417
mod test {
418
    #![allow(clippy::indexing_slicing)]
419
    use tari_common_types::seeds::cipher_seed::CipherSeed;
420
    use tari_test_utils::random::string;
421
    use tari_utilities::SafePassword;
422
    use tempfile::tempdir;
423

424
    use crate::storage::{
425
        database::WalletDatabase,
426
        sqlite_db::wallet::WalletSqliteDatabase,
427
        sqlite_utilities::run_migration_and_create_sqlite_connection,
428
    };
429

430
    #[test]
431
    fn test_database_crud() {
1✔
432
        let db_name = format!("{}.sqlite3", string(8).as_str());
1✔
433
        let db_folder = tempdir().unwrap().path().to_str().unwrap().to_string();
1✔
434
        let connection = run_migration_and_create_sqlite_connection(format!("{db_folder}{db_name}"), 16).unwrap();
1✔
435

436
        let passphrase = SafePassword::from("my secret lovely passphrase");
1✔
437
        let db = WalletDatabase::new(WalletSqliteDatabase::new(connection, passphrase).unwrap());
1✔
438

439
        // Test wallet settings
440
        assert!(db.get_master_seed().unwrap().is_none());
1✔
441
        let seed = CipherSeed::random();
1✔
442
        db.set_master_seed(seed.clone()).unwrap();
1✔
443
        let stored_seed = db.get_master_seed().unwrap().unwrap();
1✔
444
        assert_eq!(seed, stored_seed);
1✔
445
        db.clear_master_seed().unwrap();
1✔
446
        assert!(db.get_master_seed().unwrap().is_none());
1✔
447

448
        let client_key_values = vec![
1✔
449
            ("key1".to_string(), "value1".to_string()),
1✔
450
            ("key2".to_string(), "value2".to_string()),
1✔
451
            ("key3".to_string(), "value3".to_string()),
1✔
452
        ];
453

454
        for kv in &client_key_values {
3✔
455
            db.set_client_key_value(kv.0.clone(), kv.1.clone()).unwrap();
3✔
456
        }
3✔
457

458
        assert!(db.get_client_key_value("wrong".to_string()).unwrap().is_none());
1✔
459

460
        db.set_client_key_value(client_key_values[0].0.clone(), "updated".to_string())
1✔
461
            .unwrap();
1✔
462

463
        assert_eq!(
1✔
464
            db.get_client_key_value(client_key_values[0].0.clone())
1✔
465
                .unwrap()
1✔
466
                .unwrap(),
1✔
467
            "updated".to_string()
1✔
468
        );
469

470
        assert!(!db.clear_client_value("wrong".to_string()).unwrap());
1✔
471

472
        assert!(db.clear_client_value(client_key_values[0].0.clone()).unwrap());
1✔
473

474
        assert!(!db.clear_client_value(client_key_values[0].0.clone()).unwrap());
1✔
475
    }
1✔
476
}
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