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

tari-project / tari / 23184374285

17 Mar 2026 08:04AM UTC coverage: 60.967% (-0.8%) from 61.722%
23184374285

push

github

SWvheerden
chore: new release v5.3.0-pre.3

69750 of 114406 relevant lines covered (60.97%)

227024.12 hits per line

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

85.65
/base_layer/core/src/chain_storage/lmdb_db/validator_node_store.rs
1
//  Copyright 2022. 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
// CompressedPublicKey in BTreeSet results in a mutable key type, however there link is not applicable
24
#![allow(clippy::mutable_key_type)]
25

26
use std::{collections::BTreeSet, ops::Deref};
27

28
use lmdb_zero::{ConstTransaction, WriteTransaction};
29
use log::*;
30
use serde::de::DeserializeOwned;
31
use tari_common_types::{epoch::VnEpoch, types::CompressedPublicKey};
32
use tari_storage::lmdb_store::DatabaseRef;
33
use tari_utilities::ByteArray;
34

35
use crate::chain_storage::{
36
    ChainStorageError,
37
    ValidatorNodeEntry,
38
    lmdb_db::{
39
        composite_key::CompositeKey,
40
        cursors::{FromKeyBytes, LmdbReadCursor},
41
        helpers,
42
        lmdb::{lmdb_delete, lmdb_delete_key_value, lmdb_exists, lmdb_get, lmdb_insert, lmdb_insert_dup, lmdb_len},
43
    },
44
};
45

46
const LOG_TARGET: &str = "c::cs::lmdb_db::validator_node_store";
47

48
const U64_SIZE: usize = size_of::<u64>();
49
const PK_SIZE: usize = 32;
50

51
/// <sid, pk>
52
type ValidatorNodeStoreKey = CompositeKey<{ PK_SIZE + PK_SIZE }>;
53
/// <sid, epoch, pk>
54
type ExitQueueKey = CompositeKey<{ PK_SIZE + U64_SIZE + PK_SIZE }>;
55
const EXIT_QUEUE_KEY_SECTIONS: [usize; 3] = [PK_SIZE, U64_SIZE, PK_SIZE];
56
/// <sid, epoch> DUPSORT
57
type ActivationQueueKey = CompositeKey<{ PK_SIZE + U64_SIZE }>;
58
const ACTIVATION_QUEUE_KEY_SECTIONS: [usize; 2] = [PK_SIZE, U64_SIZE];
59

60
pub struct ValidatorNodeStore<'a, Txn> {
61
    txn: &'a Txn,
62
    db_validator_nodes: DatabaseRef,
63
    db_validator_activation_queue: DatabaseRef,
64
    db_validator_nodes_exit: DatabaseRef,
65
}
66

67
impl<'a, Txn: Deref<Target = ConstTransaction<'a>>> ValidatorNodeStore<'a, Txn> {
68
    pub fn new(
15✔
69
        txn: &'a Txn,
15✔
70
        db_validator_nodes: DatabaseRef,
15✔
71
        db_validator_activation_queue: DatabaseRef,
15✔
72
        db_validator_nodes_exit: DatabaseRef,
15✔
73
    ) -> Self {
15✔
74
        Self {
15✔
75
            txn,
15✔
76
            db_validator_nodes,
15✔
77
            db_validator_activation_queue,
15✔
78
            db_validator_nodes_exit,
15✔
79
        }
15✔
80
    }
15✔
81
}
82

83
impl ValidatorNodeStore<'_, WriteTransaction<'_>> {
84
    pub fn insert(&self, validator: &ValidatorNodeEntry) -> Result<(), ChainStorageError> {
61✔
85
        let key = create_activation_key(validator.sidechain_public_key.as_ref(), validator.activation_epoch);
61✔
86
        lmdb_insert_dup(
61✔
87
            self.txn,
61✔
88
            &self.db_validator_activation_queue,
61✔
89
            &key,
61✔
90
            &validator.public_key,
61✔
91
        )?;
×
92

93
        let key = create_vn_key(validator.sidechain_public_key.as_ref(), &validator.public_key);
61✔
94
        lmdb_insert(self.txn, &self.db_validator_nodes, &key, validator, "Validator node")?;
61✔
95

96
        Ok(())
59✔
97
    }
61✔
98

99
    pub fn delete(
×
100
        &self,
×
101
        sidechain_pk: Option<&CompressedPublicKey>,
×
102
        public_key: &CompressedPublicKey,
×
103
    ) -> Result<(), ChainStorageError> {
×
104
        let key = create_vn_key(sidechain_pk, public_key);
×
105
        let vn = lmdb_get::<_, ValidatorNodeEntry>(self.txn, &self.db_validator_nodes, &key)?.ok_or_else(|| {
×
106
            ChainStorageError::ValueNotFound {
×
107
                entity: "Validator node (delete)",
×
108
                field: "public key",
×
109
                value: public_key.to_string(),
×
110
            }
×
111
        })?;
×
112
        lmdb_delete(self.txn, &self.db_validator_nodes, &key, "validator_nodes")?;
×
113

114
        let key = create_activation_key(sidechain_pk, vn.activation_epoch);
×
115
        lmdb_delete_key_value(self.txn, &self.db_validator_activation_queue, &key, &vn.public_key)?;
×
116

117
        Ok(())
×
118
    }
×
119

120
    pub fn exit(
22✔
121
        &self,
22✔
122
        sidechain_pk: Option<&CompressedPublicKey>,
22✔
123
        exit_node: &CompressedPublicKey,
22✔
124
        exit_epoch: VnEpoch,
22✔
125
    ) -> Result<(), ChainStorageError> {
22✔
126
        let vn_key = create_vn_key(sidechain_pk, exit_node);
22✔
127
        if !lmdb_exists(self.txn, &self.db_validator_nodes, &vn_key)? {
22✔
128
            return Err(ChainStorageError::ValueNotFound {
1✔
129
                entity: "Validator node (exit)",
1✔
130
                field: "public key",
1✔
131
                value: exit_node.to_string(),
1✔
132
            });
1✔
133
        }
21✔
134
        let vn = lmdb_get::<_, ValidatorNodeEntry>(self.txn, &self.db_validator_nodes, &vn_key)?
21✔
135
            .expect("Found node key but not node in db_validator_nodes");
21✔
136

137
        lmdb_delete(self.txn, &self.db_validator_nodes, &vn_key, "validator_nodes")?;
21✔
138

139
        let key = create_exit_queue_key(sidechain_pk, exit_epoch, exit_node);
21✔
140
        lmdb_insert(
21✔
141
            self.txn,
21✔
142
            &self.db_validator_nodes_exit,
21✔
143
            &key,
21✔
144
            &vn,
21✔
145
            "validator_nodes_exit",
146
        )?;
×
147
        Ok(())
21✔
148
    }
22✔
149

150
    pub fn undo_exit(
3✔
151
        &self,
3✔
152
        sidechain_pk: Option<&CompressedPublicKey>,
3✔
153
        min_epoch: VnEpoch,
3✔
154
        exit_node: &CompressedPublicKey,
3✔
155
    ) -> Result<(), ChainStorageError> {
3✔
156
        let mut epoch = min_epoch;
3✔
157
        let sidechain_pk_bytes = sid_as_slice(sidechain_pk);
3✔
158
        // Search through the epochs, from the min until we have no more records
159
        let (exit_key, vn) = loop {
1✔
160
            {
161
                // This is to check if there are possibly more records for the next epoch - if not we exit early with an
162
                // error. If we didnt do this, the loop would be endless if min_epoch/exit_node do not
163
                // exist.
164
                let mut cursor = self.exit_queue_read_cursor()?;
8✔
165
                let epoch_prefix = create_exit_queue_prefix_key(sidechain_pk, epoch);
8✔
166
                cursor.seek_range(&epoch_prefix)?;
8✔
167

168
                let key = cursor.next_key()?.ok_or_else(||
8✔
169
                    // Not in this epoch, and nothing in subsequent recs
170
                    ChainStorageError::ValueNotFound {
171
                        entity: "Validator node (undo exit)",
172
                        field: "public key (undo exit)",
173
                        value: exit_node.to_string(),
×
174
                    })?;
×
175
                let mut sections = key.section_iter(EXIT_QUEUE_KEY_SECTIONS);
8✔
176
                let sidechain = sections
8✔
177
                    .next()
8✔
178
                    .ok_or_else(|| ChainStorageError::DataInconsistencyDetected {
8✔
179
                        function: "ValidatorNodeStore::undo_exit",
180
                        details: "Malformed exit queue key".to_string(),
×
181
                    })?;
×
182
                if sidechain != sidechain_pk_bytes {
8✔
183
                    return Err(ChainStorageError::ValueNotFound {
2✔
184
                        entity: "Validator node (undo exit)",
2✔
185
                        field: "public key (undo exit)",
2✔
186
                        value: exit_node.to_string(),
2✔
187
                    });
2✔
188
                }
6✔
189
            }
190

191
            let exit_key = create_exit_queue_key(sidechain_pk, epoch, exit_node);
6✔
192
            let vn = lmdb_get::<_, ValidatorNodeEntry>(self.txn, &self.db_validator_nodes_exit, &exit_key)?;
6✔
193
            if let Some(vn) = vn {
6✔
194
                break (exit_key, vn);
1✔
195
            }
5✔
196
            epoch += VnEpoch(1);
5✔
197
        };
198

199
        lmdb_delete(
1✔
200
            self.txn,
1✔
201
            &self.db_validator_nodes_exit,
1✔
202
            &exit_key,
1✔
203
            "validator_nodes_exit",
204
        )?;
×
205

206
        // Re-insert. Since we know the node was already activated in the past, we do not need to re-insert into the
207
        // historical activation queue.
208
        let key = create_vn_key(sidechain_pk, &vn.public_key);
1✔
209
        lmdb_insert(self.txn, &self.db_validator_nodes, &key, &vn, "Validator node")?;
1✔
210

211
        Ok(())
1✔
212
    }
3✔
213
}
214

215
impl<'a, Txn: Deref<Target = ConstTransaction<'a>>> ValidatorNodeStore<'a, Txn> {
216
    fn validator_store_cursor(
7✔
217
        &self,
7✔
218
    ) -> Result<LmdbReadCursor<'a, ValidatorNodeStoreKey, ValidatorNodeEntry>, ChainStorageError> {
7✔
219
        self.new_read_cursor(self.db_validator_nodes.clone())
7✔
220
    }
7✔
221

222
    fn activation_queue_read_cursor(
21✔
223
        &self,
21✔
224
    ) -> Result<LmdbReadCursor<'a, ActivationQueueKey, CompressedPublicKey>, ChainStorageError> {
21✔
225
        self.new_read_cursor(self.db_validator_activation_queue.clone())
21✔
226
    }
21✔
227

228
    fn exit_queue_read_cursor(
50✔
229
        &self,
50✔
230
    ) -> Result<LmdbReadCursor<'a, ExitQueueKey, ValidatorNodeEntry>, ChainStorageError> {
50✔
231
        self.new_read_cursor(self.db_validator_nodes_exit.clone())
50✔
232
    }
50✔
233

234
    fn new_read_cursor<K: FromKeyBytes, V: DeserializeOwned>(
78✔
235
        &self,
78✔
236
        db: DatabaseRef,
78✔
237
    ) -> Result<LmdbReadCursor<'a, K, V>, ChainStorageError> {
78✔
238
        let cursor = self.txn.cursor(db)?;
78✔
239
        let access = self.txn.access();
78✔
240
        let cursor = LmdbReadCursor::new(cursor, access);
78✔
241
        Ok(cursor)
78✔
242
    }
78✔
243

244
    /// Checks if the given validator node (by its public key and side chain ID)
245
    /// exists until a given `end_epoch`.
246
    pub fn vn_exists(
×
247
        &self,
×
248
        sidechain_id: Option<&CompressedPublicKey>,
×
249
        public_key: &CompressedPublicKey,
×
250
        end_epoch: VnEpoch,
×
251
    ) -> Result<bool, ChainStorageError> {
×
252
        let key = create_vn_key(sidechain_id, public_key);
×
253
        let Some(vn) = lmdb_get::<_, ValidatorNodeEntry>(self.txn, &self.db_validator_nodes, &key)? else {
×
254
            return Ok(false);
×
255
        };
256

257
        Ok(vn.registration_epoch <= end_epoch)
×
258
    }
×
259

260
    /// Checks if the given validator node (by its public key and side chain ID)
261
    /// exists until a given `end_epoch`.
262
    pub fn is_vn_active(
13✔
263
        &self,
13✔
264
        sidechain_id: Option<&CompressedPublicKey>,
13✔
265
        public_key: &CompressedPublicKey,
13✔
266
        end_epoch: VnEpoch,
13✔
267
    ) -> Result<bool, ChainStorageError> {
13✔
268
        let key = create_vn_key(sidechain_id, public_key);
13✔
269
        match lmdb_get::<_, ValidatorNodeEntry>(self.txn, &self.db_validator_nodes, &key)? {
13✔
270
            Some(vn) => {
5✔
271
                debug!(target: LOG_TARGET, "Found validator node in store: {} (activated: {}, end: {})", public_key, vn.activation_epoch, end_epoch);
5✔
272
                Ok(vn.activation_epoch <= end_epoch)
5✔
273
            },
274
            None => {
275
                debug!(target: LOG_TARGET, "Validator node not found in store: {public_key}, checking exit queue");
8✔
276
                let key = create_exit_queue_prefix_key(sidechain_id, end_epoch);
8✔
277
                let mut cursor = self.exit_queue_read_cursor()?;
8✔
278
                cursor.seek_range(&key)?;
8✔
279
                let sidechain_bytes = sid_as_slice(sidechain_id);
8✔
280
                // TODO: This is O(n) where n is the number of validators in the exit queue for the sid at or after
281
                // end_epoch.
282
                while let Some(key) = cursor.next_key()? {
17✔
283
                    let mut sections = key.section_iter(EXIT_QUEUE_KEY_SECTIONS);
17✔
284
                    let sidechain_pk = sections
17✔
285
                        .next()
17✔
286
                        .ok_or_else(|| ChainStorageError::DataInconsistencyDetected {
17✔
287
                            function: "ValidatorNodeStore::is_vn_active",
288
                            details: "Malformed exit queue key".to_string(),
×
289
                        })?;
×
290
                    if sidechain_pk != sidechain_bytes {
17✔
291
                        break;
1✔
292
                    }
16✔
293

294
                    let key_epoch =
16✔
295
                        sections
16✔
296
                            .next_be_u64()
16✔
297
                            .ok_or_else(|| ChainStorageError::DataInconsistencyDetected {
16✔
298
                                function: "ValidatorNodeStore::is_vn_active",
299
                                details: "Malformed exit queue key".to_string(),
×
300
                            })?;
×
301

302
                    let pk = sections
16✔
303
                        .next()
16✔
304
                        .ok_or_else(|| ChainStorageError::DataInconsistencyDetected {
16✔
305
                            function: "ValidatorNodeStore::is_vn_active",
306
                            details: "Malformed exit queue key".to_string(),
×
307
                        })?;
×
308
                    if pk != public_key.as_bytes() {
16✔
309
                        continue;
9✔
310
                    }
7✔
311
                    if key_epoch <= end_epoch.as_u64() {
7✔
312
                        debug!(target: LOG_TARGET, "Found validator node in exit queue (exit applied): {public_key} (exit: {key_epoch}, end: {end_epoch})");
3✔
313
                        return Ok(false);
3✔
314
                    }
4✔
315

316
                    let (_, v) = cursor
4✔
317
                        .current()?
4✔
318
                        .expect("Cursor is not at a valid position in is_vn_active");
4✔
319

320
                    debug!(target: LOG_TARGET, "Found validator node in exit queue: {public_key} (exit: {key_epoch}, end: {end_epoch})");
4✔
321
                    return Ok(v.activation_epoch <= end_epoch);
4✔
322
                }
323

324
                debug!(target: LOG_TARGET, "Validator node not found in exit queue: {public_key}");
1✔
325
                Ok(false)
1✔
326
            },
327
        }
328
    }
13✔
329

330
    pub fn get_next_activation_epoch(
×
331
        &self,
×
332
        sidechain_id: Option<&CompressedPublicKey>,
×
333
        current_epoch: VnEpoch,
×
334
        initial_validators: usize,
×
335
        validators_per_epoch: usize,
×
336
    ) -> Result<VnEpoch, ChainStorageError> {
×
337
        // Node activates earliest in the next epoch
338
        let mut activation_epoch = current_epoch + VnEpoch(1);
×
339
        // If there are less than the initial validators, we activate all new validators in the next epoch
340
        let len = lmdb_len(self.txn, &self.db_validator_nodes)?;
×
341
        if len < initial_validators {
×
342
            return Ok(activation_epoch);
×
343
        }
×
344

345
        if validators_per_epoch == 0 {
×
346
            return Err(ChainStorageError::InvalidQuery(
×
347
                "get_next_activation_epoch: validators_per_epoch is zero, an active epoch cannot be assigned to the \
×
348
                 validator"
×
349
                    .to_string(),
×
350
            ));
×
351
        }
×
352

353
        let mut cursor = self.activation_queue_read_cursor()?;
×
354
        loop {
355
            let key = create_activation_key(sidechain_id, activation_epoch);
×
356
            if !cursor.seek_range(&key)? {
×
357
                break;
×
358
            }
×
359

360
            // No activations in this epoch
361
            if key.to_be_u64(PK_SIZE)? != activation_epoch.as_u64() {
×
362
                break;
×
363
            }
×
364

365
            // If there are less than the required number of validators in the queue for the epoch, we'll activate the
366
            // next validator in this epoch
367
            let num_queued = cursor.count_dups()?;
×
368
            if num_queued < validators_per_epoch {
×
369
                break;
×
370
            }
×
371
            activation_epoch += VnEpoch(1);
×
372
        }
373

374
        Ok(activation_epoch)
×
375
    }
×
376

377
    pub fn count_active_validators(
7✔
378
        &self,
7✔
379
        sidechain_pk: Option<&CompressedPublicKey>,
7✔
380
        end_epoch: VnEpoch,
7✔
381
    ) -> Result<usize, ChainStorageError> {
7✔
382
        let mut count = 0;
7✔
383

384
        {
385
            let mut cursor = self.validator_store_cursor()?;
7✔
386
            let sidechain_bytes = sid_as_slice(sidechain_pk);
7✔
387
            if !cursor.seek_range(sidechain_bytes)? {
7✔
388
                return Ok(0);
×
389
            }
7✔
390

391
            while let Some((key, vn)) = cursor.next()? {
24✔
392
                if key.get(..PK_SIZE).ok_or(ChainStorageError::InvalidOperation(
22✔
393
                    "Key bytes for output hash are too short".to_string(),
22✔
394
                ))? != sidechain_bytes
22✔
395
                {
396
                    // No further entries for this sidechain
397
                    break;
5✔
398
                }
17✔
399

400
                if vn.activation_epoch > end_epoch {
17✔
401
                    break;
×
402
                }
17✔
403

404
                count += 1;
17✔
405
            }
406
        }
407

408
        // We also need to search the exit queue, if the exit epoch is greater than the end epoch, the validator is
409
        // still active
410
        let mut cursor = self.exit_queue_read_cursor()?;
7✔
411
        let prefix = create_exit_queue_prefix_key(sidechain_pk, end_epoch);
7✔
412
        if !cursor.seek_range(&prefix)? {
7✔
413
            return Ok(count);
×
414
        }
7✔
415
        let sidechain_bytes = sid_as_slice(sidechain_pk);
7✔
416
        while let Some((key, _)) = cursor.next()? {
23✔
417
            let mut sections = key.section_iter(EXIT_QUEUE_KEY_SECTIONS);
21✔
418
            let sid = sections
21✔
419
                .next()
21✔
420
                .ok_or_else(|| ChainStorageError::DataInconsistencyDetected {
21✔
421
                    function: "ValidatorNodeStore::count_active_validators",
422
                    details: "Malformed exit queue key".to_string(),
×
423
                })?;
×
424

425
            if sid != sidechain_bytes {
21✔
426
                // No further entries for this sidechain
427
                break;
5✔
428
            }
16✔
429

430
            let rec_epoch = sections
16✔
431
                .next_be_u64()
16✔
432
                .ok_or_else(|| ChainStorageError::DataInconsistencyDetected {
16✔
433
                    function: "ValidatorNodeStore::count_active_validators",
434
                    details: "Malformed exit queue key".to_string(),
×
435
                })?;
×
436
            if rec_epoch <= end_epoch.as_u64() {
16✔
437
                // No further entries for this epoch
438
                continue;
6✔
439
            }
10✔
440
            count += 1;
10✔
441
        }
442

443
        Ok(count)
7✔
444
    }
7✔
445

446
    /// Returns a set of <public key, shard id> tuples ordered by epoch of registration.
447
    /// This set contains no duplicates. If a duplicate registration is found, the last registration is included.
448
    pub fn get_entire_vn_set(&self, end_epoch: VnEpoch) -> Result<BTreeSet<ValidatorNodeEntry>, ChainStorageError> {
9✔
449
        // Nodes in db_validator_nodes are not ordered by epoch, meaning retreival until an end epoch will have to
450
        // search the entire database incl > end_epoch. Instead, we first gather all public keys from the
451
        // activation queue which is ordered by epoch.
452
        let selected_nodes = {
9✔
453
            let mut cursor = self.activation_queue_read_cursor()?;
9✔
454
            cursor.seek_first()?;
9✔
455

456
            let mut selected_nodes = Vec::new();
9✔
457
            while let Some(key) = cursor.next_key()? {
18✔
458
                let mut sections = key.section_iter(ACTIVATION_QUEUE_KEY_SECTIONS);
9✔
459
                let sidechain_pk = sections
9✔
460
                    .next()
9✔
461
                    .ok_or_else(|| ChainStorageError::DataInconsistencyDetected {
9✔
462
                        function: "ValidatorNodeStore::get_entire_vn_set",
463
                        details: "Malformed activation queue key".to_string(),
×
464
                    })?;
×
465

466
                let activation_epoch =
9✔
467
                    sections
9✔
468
                        .next_be_u64()
9✔
469
                        .ok_or_else(|| ChainStorageError::DataInconsistencyDetected {
9✔
470
                            function: "ValidatorNodeStore::get_entire_vn_set",
471
                            details: "Malformed activation queue key".to_string(),
×
472
                        })?;
×
473

474
                if activation_epoch > end_epoch.as_u64() {
9✔
475
                    // Continue because we want to find the next sidechain ID - ideally we'd seek to it but that would
476
                    // require an ordered sidechain index
477
                    continue;
×
478
                }
9✔
479
                let (_, pk) = cursor
9✔
480
                    .current()?
9✔
481
                    .expect("Cursor is not at a valid position in get_entire_vn_set");
9✔
482
                let count = cursor.count_dups()?;
9✔
483
                let mut pks = Vec::with_capacity(count);
9✔
484
                // Collect all the public keys for the <sid, epoch> pair
485
                pks.push(pk);
9✔
486
                while let Some((_, pk)) = cursor.next_dup()? {
30✔
487
                    pks.push(pk)
21✔
488
                }
489

490
                let mut sid = [0u8; 32];
9✔
491
                sid.copy_from_slice(sidechain_pk);
9✔
492
                selected_nodes.push((sid, pks));
9✔
493
            }
494
            selected_nodes
9✔
495
        };
496

497
        let mut nodes = BTreeSet::new();
9✔
498

499
        for (sid, pks) in selected_nodes {
9✔
500
            for pk in pks {
30✔
501
                let key = create_vn_key_raw(&sid, pk.as_bytes());
30✔
502
                let maybe_vn = lmdb_get::<_, ValidatorNodeEntry>(self.txn, &self.db_validator_nodes, &key)?;
30✔
503
                match maybe_vn {
30✔
504
                    Some(vn) => {
12✔
505
                        nodes.insert(vn);
12✔
506
                    },
12✔
507
                    None => {
508
                        // Validator is queued for exit. Now we need to determine if the exit is before the end_epoch
509
                        // i.e an active validator at end_epoch
510
                        let mut cursor = self.exit_queue_read_cursor()?;
18✔
511
                        let prefix = create_exit_queue_prefix_key(Some(&sid), end_epoch);
18✔
512
                        if !cursor.seek_range(&prefix)? {
18✔
513
                            continue;
2✔
514
                        }
16✔
515

516
                        while let Some(key) = cursor.next_key()? {
35✔
517
                            let mut sections = key.section_iter(EXIT_QUEUE_KEY_SECTIONS);
35✔
518
                            let key_sid =
35✔
519
                                sections
35✔
520
                                    .next()
35✔
521
                                    .ok_or_else(|| ChainStorageError::DataInconsistencyDetected {
35✔
522
                                        function: "ValidatorNodeStore::get_entire_vn_set",
523
                                        details: "Malformed exit queue key".to_string(),
×
524
                                    })?;
×
525

526
                            if key_sid != sid {
35✔
527
                                // No further entries for this sidechain
528
                                break;
2✔
529
                            }
33✔
530

531
                            let key_epoch =
33✔
532
                                sections
33✔
533
                                    .next_be_u64()
33✔
534
                                    .ok_or_else(|| ChainStorageError::DataInconsistencyDetected {
33✔
535
                                        function: "ValidatorNodeStore::get_entire_vn_set",
536
                                        details: "Malformed exit queue key".to_string(),
×
537
                                    })?;
×
538

539
                            let key_pk =
33✔
540
                                sections
33✔
541
                                    .next()
33✔
542
                                    .ok_or_else(|| ChainStorageError::DataInconsistencyDetected {
33✔
543
                                        function: "ValidatorNodeStore::get_entire_vn_set",
544
                                        details: "Malformed exit queue key".to_string(),
×
545
                                    })?;
×
546

547
                            if pk.as_bytes() != key_pk {
33✔
548
                                continue;
19✔
549
                            }
14✔
550

551
                            if key_epoch > end_epoch.as_u64() {
14✔
552
                                // We've found the exit record for the validator. It is active because the exit happens
553
                                // after end_epoch PANIC: the current key exists because
554
                                // we are iterating over the keys in the exit queue
555
                                let (_, v) = cursor
8✔
556
                                    .current()?
8✔
557
                                    .expect("Cursor is not at a valid position in get_entire_vn_set");
8✔
558
                                nodes.insert(v);
8✔
559
                            }
6✔
560
                            break;
14✔
561
                        }
562
                    },
563
                };
564
            }
565
        }
566

567
        Ok(nodes)
9✔
568
    }
9✔
569

570
    /// Returns a set of <public key, shard id> tuples ordered by epoch of registration.
571
    /// This set contains no duplicates. If a duplicate registration is found, the last registration is included.
572
    pub fn get_vn_set(
1✔
573
        &self,
1✔
574
        sidechain_pk: Option<&CompressedPublicKey>,
1✔
575
        start_epoch: VnEpoch,
1✔
576
        end_epoch: VnEpoch,
1✔
577
    ) -> Result<BTreeSet<ValidatorNodeEntry>, ChainStorageError> {
1✔
578
        if end_epoch < start_epoch {
1✔
579
            return Err(ChainStorageError::InvalidQuery(format!(
×
580
                "get_vn_set: End epoch is less than start epoch: {end_epoch} < {start_epoch}"
×
581
            )));
×
582
        }
1✔
583

584
        let mut cursor = self.activation_queue_read_cursor()?;
1✔
585

586
        let start_key = create_activation_key(sidechain_pk, start_epoch);
1✔
587
        if !cursor.seek_range(&start_key)? {
1✔
588
            return Ok(BTreeSet::new());
×
589
        }
1✔
590

591
        let mut nodes = BTreeSet::new();
1✔
592
        let sidechain_bytes = sid_as_slice(sidechain_pk);
1✔
593
        while let Some((key, pk)) = cursor.next()? {
2✔
594
            if key.get(..PK_SIZE).ok_or(ChainStorageError::InvalidOperation(
1✔
595
                "Key bytes for output hash are too short".to_string(),
1✔
596
            ))? != sidechain_bytes
1✔
597
            {
598
                // No further entries for this sidechain
599
                break;
×
600
            }
1✔
601

602
            let activation_epoch = key.to_be_u64(PK_SIZE)?;
1✔
603
            let activation_epoch = VnEpoch(activation_epoch);
1✔
604
            if activation_epoch < start_epoch {
1✔
605
                continue;
×
606
            }
1✔
607
            if activation_epoch > end_epoch {
1✔
608
                break;
×
609
            }
1✔
610
            let key = create_vn_key(sidechain_pk, &pk);
1✔
611
            // Need to re-use the accessor from the cursor to double access to the transaction
612
            let bytes = cursor
1✔
613
                .access()
1✔
614
                .get(&self.db_validator_nodes, &key)
1✔
615
                .map_err(|e| ChainStorageError::AccessError(e.to_string()))?;
1✔
616
            let vn = helpers::deserialize::<ValidatorNodeEntry>(bytes)?;
1✔
617

618
            nodes.insert(vn);
1✔
619

620
            // Get remaining nodes from DUPSORT db within the same activation epoch
621
            while let Some((_, pk)) = cursor.next_dup()? {
3✔
622
                let key = create_vn_key(sidechain_pk, &pk);
2✔
623
                let bytes = cursor
2✔
624
                    .access()
2✔
625
                    .get(&self.db_validator_nodes, &key)
2✔
626
                    .map_err(|e| ChainStorageError::AccessError(e.to_string()))?;
2✔
627
                let vn = helpers::deserialize::<ValidatorNodeEntry>(bytes)?;
2✔
628
                nodes.insert(vn);
2✔
629
            }
630
        }
631

632
        Ok(nodes)
1✔
633
    }
1✔
634

635
    pub fn get_activating_in_epoch(
11✔
636
        &self,
11✔
637
        sidechain_pk: Option<&CompressedPublicKey>,
11✔
638
        epoch: VnEpoch,
11✔
639
    ) -> Result<BTreeSet<ValidatorNodeEntry>, ChainStorageError> {
11✔
640
        let keys = {
11✔
641
            let mut cursor = self.activation_queue_read_cursor()?;
11✔
642
            let key = create_activation_key(sidechain_pk, epoch);
11✔
643
            if !cursor.seek_range(&key)? {
11✔
644
                return Ok(BTreeSet::new());
×
645
            }
11✔
646

647
            let num_keys = cursor.count_dups()?;
11✔
648
            let mut keys = Vec::with_capacity(num_keys);
11✔
649
            let sidechain_bytes = sid_as_slice(sidechain_pk);
11✔
650
            while let Some((key, pk)) = cursor.next_dup()? {
21✔
651
                if *key.get(..PK_SIZE).ok_or(ChainStorageError::InvalidOperation(
18✔
652
                    "Key bytes for output hash are too short".to_string(),
18✔
653
                ))? != *sidechain_bytes
18✔
654
                {
655
                    // No further entries for this sidechain
656
                    break;
×
657
                }
18✔
658
                if key.to_be_u64(PK_SIZE)? > epoch.as_u64() {
18✔
659
                    break;
8✔
660
                }
10✔
661

662
                keys.push(create_vn_key(sidechain_pk, &pk));
10✔
663
            }
664
            keys
11✔
665
        };
666

667
        debug!(target: LOG_TARGET, "Found {} activating validators in epoch {}", keys.len(), epoch);
11✔
668

669
        let mut validators = BTreeSet::new();
11✔
670
        for key in keys {
11✔
671
            let vn = lmdb_get::<_, ValidatorNodeEntry>(self.txn, &self.db_validator_nodes, &key)?.ok_or_else(|| {
10✔
672
                ChainStorageError::DataInconsistencyDetected {
×
673
                    function: "ValidatorNodeStore::get_activating_in_epoch",
×
674
                    details: format!(
×
675
                        "Validator node in db_validator_activation_queue but not found in store for public key {key}"
×
676
                    ),
×
677
                }
×
678
            })?;
×
679

680
            validators.insert(vn);
10✔
681
        }
682

683
        Ok(validators)
11✔
684
    }
11✔
685

686
    pub fn get_exiting_in_epoch(
4✔
687
        &self,
4✔
688
        sidechain_pk: Option<&CompressedPublicKey>,
4✔
689
        epoch: VnEpoch,
4✔
690
    ) -> Result<BTreeSet<ValidatorNodeEntry>, ChainStorageError> {
4✔
691
        let mut cursor = self.exit_queue_read_cursor()?;
4✔
692
        let prefix = create_exit_queue_prefix_key(sidechain_pk, epoch);
4✔
693
        if !cursor.seek_range(&prefix)? {
4✔
694
            return Ok(BTreeSet::new());
×
695
        }
4✔
696

697
        let sidechain_bytes = sid_as_slice(sidechain_pk);
4✔
698
        let mut validators = BTreeSet::new();
4✔
699
        while let Some(key) = cursor.next_key()? {
10✔
700
            debug!(target: LOG_TARGET, "exit queue key: {key}");
9✔
701
            let mut sections = key.section_iter(EXIT_QUEUE_KEY_SECTIONS);
9✔
702
            let sid = sections
9✔
703
                .next()
9✔
704
                .ok_or_else(|| ChainStorageError::DataInconsistencyDetected {
9✔
705
                    function: "ValidatorNodeStore::get_exiting_in_epoch",
706
                    details: "Malformed exit queue key".to_string(),
×
707
                })?;
×
708

709
            if sid != sidechain_bytes {
9✔
710
                // No further entries for this sidechain
711
                break;
1✔
712
            }
8✔
713

714
            let rec_epoch = sections
8✔
715
                .next_be_u64()
8✔
716
                .ok_or_else(|| ChainStorageError::DataInconsistencyDetected {
8✔
717
                    function: "ValidatorNodeStore::get_exiting_in_epoch",
718
                    details: "Malformed exit queue key".to_string(),
×
719
                })?;
×
720
            if rec_epoch != epoch.as_u64() {
8✔
721
                debug!(target: LOG_TARGET, "No further entries for this epoch {epoch}, last rec epoch {rec_epoch}");
2✔
722
                // No further entries for this epoch
723
                break;
2✔
724
            }
6✔
725

726
            let (_, vn) = cursor
6✔
727
                .current()?
6✔
728
                .expect("Cursor is not at a valid position in get_exiting_in_epoch");
6✔
729
            debug!(target: LOG_TARGET, "Found exiting validator in epoch {}: {}", rec_epoch, vn.public_key);
6✔
730
            validators.insert(vn);
6✔
731
        }
732

733
        Ok(validators)
4✔
734
    }
4✔
735

736
    pub fn get_next_exit_epoch(
5✔
737
        &self,
5✔
738
        sidechain_pk: Option<&CompressedPublicKey>,
5✔
739
        epoch: VnEpoch,
5✔
740
        max_exits: usize,
5✔
741
    ) -> Result<VnEpoch, ChainStorageError> {
5✔
742
        if max_exits == 0 {
5✔
743
            return Err(ChainStorageError::InvalidQuery(
×
744
                "get_next_exit_epoch: max_exits is zero, an exit epoch cannot be assigned to the validator".to_string(),
×
745
            ));
×
746
        }
5✔
747

748
        let mut cursor = self.exit_queue_read_cursor()?;
5✔
749
        let prefix = create_exit_queue_prefix_key(sidechain_pk, epoch);
5✔
750
        if !cursor.seek_range(&prefix)? {
5✔
751
            return Ok(epoch);
2✔
752
        }
3✔
753

754
        let sidechain_bytes = sid_as_slice(sidechain_pk);
3✔
755
        let mut exit_count = 0;
3✔
756
        let mut exit_epoch = epoch;
3✔
757
        while let Some(key) = cursor.next_key()? {
16✔
758
            trace!(target: LOG_TARGET, "exit queue key: {key}");
16✔
759
            let mut sections = key.section_iter(EXIT_QUEUE_KEY_SECTIONS);
16✔
760
            let sid = sections
16✔
761
                .next()
16✔
762
                .ok_or_else(|| ChainStorageError::DataInconsistencyDetected {
16✔
763
                    function: "ValidatorNodeStore::get_exiting_in_epoch",
764
                    details: "Malformed exit queue key".to_string(),
×
765
                })?;
×
766

767
            if sid != sidechain_bytes {
16✔
768
                // No further entries for this sidechain
769
                break;
2✔
770
            }
14✔
771

772
            let rec_epoch = sections
14✔
773
                .next_be_u64()
14✔
774
                .ok_or_else(|| ChainStorageError::DataInconsistencyDetected {
14✔
775
                    function: "ValidatorNodeStore::get_exiting_in_epoch",
776
                    details: "Malformed exit queue key".to_string(),
×
777
                })?;
×
778

779
            if rec_epoch == exit_epoch.as_u64() {
14✔
780
                exit_count += 1;
13✔
781
                if exit_count >= max_exits {
13✔
782
                    // Scan to the next epoch
783
                    exit_epoch += VnEpoch(1);
6✔
784
                    cursor.seek_range(&create_exit_queue_prefix_key(sidechain_pk, exit_epoch))?;
6✔
785
                    exit_count = 0;
6✔
786
                }
7✔
787
            } else {
788
                // Epoch has changed - first check if the previous epoch was below the max_exits
789
                if exit_count < max_exits {
1✔
790
                    break;
1✔
791
                }
×
792
                exit_epoch = VnEpoch(rec_epoch);
×
793
                exit_count = 1;
×
794
            }
795
        }
796

797
        Ok(exit_epoch)
3✔
798
    }
5✔
799

800
    pub fn get(
1✔
801
        &self,
1✔
802
        sidechain_pk: Option<&CompressedPublicKey>,
1✔
803
        public_key: &CompressedPublicKey,
1✔
804
    ) -> Result<Option<ValidatorNodeEntry>, ChainStorageError> {
1✔
805
        let key = create_vn_key(sidechain_pk, public_key);
1✔
806
        let vn = lmdb_get::<_, ValidatorNodeEntry>(self.txn, &self.db_validator_nodes, &key)?;
1✔
807
        Ok(vn)
1✔
808
    }
1✔
809
}
810

811
fn create_vn_key(
111✔
812
    sidechain_pk: Option<&CompressedPublicKey>,
111✔
813
    public_key: &CompressedPublicKey,
111✔
814
) -> ValidatorNodeStoreKey {
111✔
815
    create_vn_key_raw(sid_as_slice(sidechain_pk), public_key.as_bytes())
111✔
816
}
111✔
817
fn create_vn_key_raw(sidechain_pk: &[u8], public_key: &[u8]) -> ValidatorNodeStoreKey {
141✔
818
    ValidatorNodeStoreKey::try_from_parts(&[sidechain_pk, public_key])
141✔
819
        .expect("create_vn_key_raw: Composite key length is incorrect")
141✔
820
}
141✔
821

822
fn create_exit_queue_key(
27✔
823
    sidechain_pk: Option<&CompressedPublicKey>,
27✔
824
    epoch: VnEpoch,
27✔
825
    public_key: &CompressedPublicKey,
27✔
826
) -> ExitQueueKey {
27✔
827
    ExitQueueKey::try_from_parts(&[
27✔
828
        sid_as_slice(sidechain_pk),
27✔
829
        epoch.to_be_bytes().as_slice(),
27✔
830
        public_key.as_bytes(),
27✔
831
    ])
27✔
832
    .expect("create_exit_queue_key: Composite key length is incorrect")
27✔
833
}
27✔
834

835
fn create_exit_queue_prefix_key<B: ByteArray>(sidechain_pk: Option<&B>, epoch: VnEpoch) -> [u8; PK_SIZE + U64_SIZE] {
56✔
836
    let mut buf = [0u8; PK_SIZE + U64_SIZE];
56✔
837
    if let Some(pk) = sidechain_pk {
56✔
838
        buf.get_mut(..PK_SIZE)
24✔
839
            .expect("Should exists")
24✔
840
            .copy_from_slice(pk.as_bytes());
24✔
841
    }
32✔
842
    buf.get_mut(PK_SIZE..)
56✔
843
        .expect("Should exists")
56✔
844
        .copy_from_slice(&epoch.to_be_bytes());
56✔
845
    buf
56✔
846
}
56✔
847

848
fn create_activation_key(sidechain_pk: Option<&CompressedPublicKey>, epoch: VnEpoch) -> ActivationQueueKey {
73✔
849
    ActivationQueueKey::try_from_parts(&[sid_as_slice(sidechain_pk), &epoch.to_be_bytes()])
73✔
850
        .expect("create_activation_key: Composite key length is incorrect")
73✔
851
}
73✔
852

853
fn sid_as_slice(sidechain_pk: Option<&CompressedPublicKey>) -> &[u8] {
255✔
854
    sidechain_pk.map_or([0u8; 32].as_slice(), |pk| pk.as_bytes())
255✔
855
}
255✔
856

857
#[cfg(test)]
858
mod tests {
859
    #![allow(clippy::indexing_slicing)]
860
    use lmdb_zero::db;
861
    use tari_common_types::types::CompressedCommitment;
862
    use tari_test_utils::unpack_enum;
863

864
    use super::*;
865
    use crate::{
866
        chain_storage::tests::temp_db::TempLmdbDatabase,
867
        test_helpers::{make_hash, new_public_key},
868
    };
869

870
    const DBS: &[(&str, db::Flags)] = &[
871
        ("validator_node_store", db::CREATE),
872
        ("validator_node_activation_queue", db::DUPSORT),
873
        ("validator_node_exit_queue", db::CREATE),
874
    ];
875

876
    fn create_store<'a, Txn: Deref<Target = ConstTransaction<'a>>>(
9✔
877
        db: &TempLmdbDatabase,
9✔
878
        txn: &'a Txn,
9✔
879
    ) -> ValidatorNodeStore<'a, Txn> {
9✔
880
        let store_db = db.get_db(DBS[0].0).clone();
9✔
881
        let activation_queue = db.get_db(DBS[1].0).clone();
9✔
882
        let exit_db = db.get_db(DBS[2].0).clone();
9✔
883
        ValidatorNodeStore::new(txn, store_db, activation_queue, exit_db)
9✔
884
    }
9✔
885

886
    fn insert_n_vns(
17✔
887
        store: &ValidatorNodeStore<'_, WriteTransaction<'_>>,
17✔
888
        start_epoch: u64,
17✔
889
        epoch_increment: u64,
17✔
890
        n: usize,
17✔
891
        sidechain_id: Option<&CompressedPublicKey>,
17✔
892
    ) -> Vec<ValidatorNodeEntry> {
17✔
893
        let mut nodes = Vec::with_capacity(n);
17✔
894
        for i in 0..n {
55✔
895
            let public_key = new_public_key();
55✔
896
            let shard_key = make_hash(public_key.as_bytes());
55✔
897
            let start_epoch = VnEpoch(start_epoch + (i as u64 * epoch_increment));
55✔
898
            let entry = ValidatorNodeEntry {
55✔
899
                public_key: public_key.clone(),
55✔
900
                shard_key,
55✔
901
                commitment: CompressedCommitment::from_compressed_key(new_public_key()),
55✔
902
                activation_epoch: start_epoch,
55✔
903
                sidechain_public_key: sidechain_id.cloned(),
55✔
904
                ..Default::default()
55✔
905
            };
55✔
906
            store.insert(&entry).unwrap();
55✔
907
            nodes.push(entry);
55✔
908
        }
55✔
909
        nodes.sort_by(|a, b| {
51✔
910
            a.sidechain_public_key
51✔
911
                .cmp(&b.sidechain_public_key)
51✔
912
                .then(a.shard_key.cmp(&b.shard_key))
51✔
913
        });
51✔
914
        nodes
17✔
915
    }
17✔
916

917
    mod insert {
918
        use super::*;
919

920
        #[test]
921
        fn it_inserts_validator_nodes() {
1✔
922
            let db = TempLmdbDatabase::with_dbs(DBS);
1✔
923
            let txn = db.write_transaction();
1✔
924
            let store = create_store(&db, &txn);
1✔
925
            let nodes = insert_n_vns(&store, 1, 0, 3, None);
1✔
926
            let set = store.get_vn_set(None, VnEpoch(1), VnEpoch(3)).unwrap();
1✔
927
            for (i, node) in set.iter().enumerate() {
3✔
928
                assert_eq!(*node, nodes[i]);
3✔
929
            }
930
            assert_eq!(set.len(), 3);
1✔
931
        }
1✔
932

933
        #[test]
934
        fn it_does_not_allow_duplicate_entries() {
1✔
935
            let db = TempLmdbDatabase::with_dbs(DBS);
1✔
936
            let txn = db.write_transaction();
1✔
937
            let store = create_store(&db, &txn);
1✔
938
            let p1 = new_public_key();
1✔
939
            let entry = ValidatorNodeEntry {
1✔
940
                shard_key: make_hash(p1.as_bytes()),
1✔
941
                public_key: p1,
1✔
942
                commitment: CompressedCommitment::from_compressed_key(new_public_key()),
1✔
943
                ..Default::default()
1✔
944
            };
1✔
945
            store.insert(&entry).unwrap();
1✔
946
            let err = store.insert(&entry).unwrap_err();
1✔
947
            unpack_enum!(ChainStorageError::KeyExists { .. } = err);
1✔
948
        }
1✔
949

950
        #[test]
951
        fn it_returns_key_exists_if_duplicate_inserted() {
1✔
952
            let db = TempLmdbDatabase::with_dbs(DBS);
1✔
953
            let txn = db.write_transaction();
1✔
954
            let store = create_store(&db, &txn);
1✔
955
            let nodes = insert_n_vns(&store, 1, 0, 3, None);
1✔
956
            // Node 0 and 1 re-register at height 4
957

958
            let s0 = make_hash(nodes[0].shard_key);
1✔
959
            let err = store
1✔
960
                .insert(&ValidatorNodeEntry {
1✔
961
                    public_key: nodes[0].public_key.clone(),
1✔
962
                    shard_key: s0,
1✔
963
                    commitment: CompressedCommitment::from_compressed_key(new_public_key().clone()),
1✔
964
                    ..Default::default()
1✔
965
                })
1✔
966
                .unwrap_err();
1✔
967
            assert!(matches!(err, ChainStorageError::KeyExists { .. }));
1✔
968
        }
1✔
969
    }
970

971
    mod get {
972
        use super::*;
973

974
        #[test]
975
        fn it_returns_the_validator_node() {
1✔
976
            let db = TempLmdbDatabase::with_dbs(DBS);
1✔
977
            let txn = db.write_transaction();
1✔
978
            let store = create_store(&db, &txn);
1✔
979
            let nodes = insert_n_vns(&store, 1, 0, 3, None);
1✔
980

981
            let s = store.get(None, &nodes[0].public_key).unwrap().unwrap();
1✔
982
            assert_eq!(s, nodes[0]);
1✔
983
        }
1✔
984
    }
985

986
    mod activating_in_epoch {
987
        use super::*;
988

989
        #[test]
990
        fn it_returns_vns_activating_in_epoch() {
1✔
991
            let db = TempLmdbDatabase::with_dbs(DBS);
1✔
992
            let txn = db.write_transaction();
1✔
993
            let store = create_store(&db, &txn);
1✔
994
            let nodes = insert_n_vns(&store, 1, 0, 3, None);
1✔
995
            let nodes2 = insert_n_vns(&store, 10, 0, 4, None);
1✔
996
            let sid = new_public_key();
1✔
997
            let nodes3 = insert_n_vns(&store, 1, 0, 3, Some(&sid));
1✔
998

999
            let epoch = VnEpoch(1);
1✔
1000
            let activating = store.get_activating_in_epoch(None, epoch).unwrap();
1✔
1001
            assert_eq!(activating.len(), 3);
1✔
1002
            for (i, node) in activating.iter().enumerate() {
3✔
1003
                assert_eq!(*node, nodes[i]);
3✔
1004
            }
1005

1006
            for epoch in 2..10 {
8✔
1007
                let activating = store.get_activating_in_epoch(None, VnEpoch(epoch)).unwrap();
8✔
1008
                assert_eq!(activating.len(), 0);
8✔
1009
            }
1010

1011
            let epoch = VnEpoch(10);
1✔
1012
            let activating = store.get_activating_in_epoch(None, epoch).unwrap();
1✔
1013
            assert_eq!(activating.len(), 4);
1✔
1014
            for (i, node) in activating.iter().enumerate() {
4✔
1015
                assert_eq!(*node, nodes2[i]);
4✔
1016
            }
1017

1018
            let epoch = VnEpoch(1);
1✔
1019
            let activating = store.get_activating_in_epoch(Some(&sid), epoch).unwrap();
1✔
1020
            assert_eq!(activating.len(), 3);
1✔
1021
            for (i, node) in activating.iter().enumerate() {
3✔
1022
                assert_eq!(*node, nodes3[i]);
3✔
1023
            }
1024
        }
1✔
1025
    }
1026

1027
    mod get_exiting_in_epoch {
1028
        use super::*;
1029
        use crate::test_helpers::make_hash2;
1030

1031
        #[test]
1032
        fn it_returns_vns_exiting() {
1✔
1033
            let db = TempLmdbDatabase::with_dbs(DBS);
1✔
1034
            let txn = db.write_transaction();
1✔
1035
            let store = create_store(&db, &txn);
1✔
1036
            let nodes = insert_n_vns(&store, 1, 0, 3, None);
1✔
1037
            let nodes2 = insert_n_vns(&store, 10, 0, 4, None);
1✔
1038
            let sid = new_public_key();
1✔
1039
            let nodes3 = insert_n_vns(&store, 1, 0, 3, Some(&sid));
1✔
1040

1041
            assert!(store.is_vn_active(None, &nodes[0].public_key, VnEpoch(1)).unwrap());
1✔
1042
            assert!(!store.is_vn_active(None, &nodes2[0].public_key, VnEpoch(1)).unwrap());
1✔
1043
            assert!(store.is_vn_active(None, &nodes2[0].public_key, VnEpoch(11)).unwrap());
1✔
1044
            assert!(
1✔
1045
                store
1✔
1046
                    .is_vn_active(Some(&sid), &nodes3[0].public_key, VnEpoch(3))
1✔
1047
                    .unwrap()
1✔
1048
            );
1049

1050
            // Exit some nodes
1051
            store.exit(None, &nodes[0].public_key, VnEpoch(11)).unwrap();
1✔
1052
            store.exit(None, &nodes[0].public_key, VnEpoch(11)).unwrap_err();
1✔
1053

1054
            store.exit(None, &nodes[1].public_key, VnEpoch(11)).unwrap();
1✔
1055
            store.exit(None, &nodes2[0].public_key, VnEpoch(110)).unwrap();
1✔
1056
            store.exit(None, &nodes2[1].public_key, VnEpoch(110)).unwrap();
1✔
1057
            store.exit(Some(&sid), &nodes3[0].public_key, VnEpoch(11)).unwrap();
1✔
1058
            store.exit(Some(&sid), &nodes3[1].public_key, VnEpoch(11)).unwrap();
1✔
1059

1060
            let next_exit_epoch = store.get_next_exit_epoch(None, VnEpoch(11), 2).unwrap();
1✔
1061
            assert_eq!(next_exit_epoch, VnEpoch(12));
1✔
1062

1063
            assert!(store.is_vn_active(None, &nodes[0].public_key, VnEpoch(9)).unwrap());
1✔
1064
            assert!(!store.is_vn_active(None, &nodes[0].public_key, VnEpoch(12)).unwrap());
1✔
1065

1066
            let count = store.count_active_validators(None, VnEpoch(10)).unwrap();
1✔
1067
            assert_eq!(count, 7);
1✔
1068
            let count = store.count_active_validators(None, VnEpoch(11)).unwrap();
1✔
1069
            assert_eq!(count, 5);
1✔
1070
            let count = store.count_active_validators(Some(&sid), VnEpoch(10)).unwrap();
1✔
1071
            assert_eq!(count, 3);
1✔
1072
            let count = store.count_active_validators(Some(&sid), VnEpoch(11)).unwrap();
1✔
1073
            assert_eq!(count, 1);
1✔
1074
            let count = store.count_active_validators(None, VnEpoch(109)).unwrap();
1✔
1075
            assert_eq!(count, 5);
1✔
1076
            let count = store.count_active_validators(None, VnEpoch(110)).unwrap();
1✔
1077
            assert_eq!(count, 3);
1✔
1078
            let count = store.count_active_validators(None, VnEpoch(111)).unwrap();
1✔
1079
            assert_eq!(count, 3);
1✔
1080

1081
            let exiting = store.get_exiting_in_epoch(None, VnEpoch(11)).unwrap();
1✔
1082
            assert_eq!(exiting.len(), 2);
1✔
1083
            for (i, node) in exiting.iter().enumerate() {
2✔
1084
                assert_eq!(*node, nodes[i]);
2✔
1085
            }
1086
            assert!(store.is_vn_active(None, &nodes[0].public_key, VnEpoch(10)).unwrap());
1✔
1087
            assert!(!store.is_vn_active(None, &nodes[0].public_key, VnEpoch(11)).unwrap());
1✔
1088

1089
            let exiting = store.get_exiting_in_epoch(None, VnEpoch(110)).unwrap();
1✔
1090
            assert_eq!(exiting.len(), 2);
1✔
1091
            for (i, node) in exiting.iter().enumerate() {
2✔
1092
                assert_eq!(*node, nodes2[i]);
2✔
1093
            }
1094
            assert!(store.is_vn_active(None, &nodes2[0].public_key, VnEpoch(109)).unwrap());
1✔
1095
            assert!(!store.is_vn_active(None, &nodes2[0].public_key, VnEpoch(110)).unwrap());
1✔
1096

1097
            let exiting = store.get_exiting_in_epoch(Some(&sid), VnEpoch(10)).unwrap();
1✔
1098
            assert_eq!(exiting.len(), 0);
1✔
1099
            let exiting = store.get_exiting_in_epoch(Some(&sid), VnEpoch(11)).unwrap();
1✔
1100
            assert_eq!(exiting.len(), 2);
1✔
1101
            for (i, node) in exiting.iter().enumerate() {
2✔
1102
                assert_eq!(*node, nodes3[i]);
2✔
1103
            }
1104
            assert!(
1✔
1105
                store
1✔
1106
                    .is_vn_active(Some(&sid), &nodes3[0].public_key, VnEpoch(10))
1✔
1107
                    .unwrap()
1✔
1108
            );
1109
            assert!(
1✔
1110
                !store
1✔
1111
                    .is_vn_active(Some(&sid), &nodes3[0].public_key, VnEpoch(11))
1✔
1112
                    .unwrap()
1✔
1113
            );
1114
        }
1✔
1115

1116
        #[test]
1117
        fn it_returns_then_next_exit_epoch() {
1✔
1118
            let db = TempLmdbDatabase::with_dbs(DBS);
1✔
1119
            let txn = db.write_transaction();
1✔
1120
            let store = create_store(&db, &txn);
1✔
1121
            let nodes = insert_n_vns(&store, 1, 0, 3, None);
1✔
1122
            let nodes2 = insert_n_vns(&store, 10, 0, 4, None);
1✔
1123
            let sid = new_public_key();
1✔
1124
            let nodes3 = insert_n_vns(&store, 1, 0, 3, Some(&sid));
1✔
1125
            let nodes4 = insert_n_vns(&store, 1, 0, 3, None);
1✔
1126

1127
            // Empty
1128
            let next_exit_epoch = store.get_next_exit_epoch(None, VnEpoch(10), 2).unwrap();
1✔
1129
            assert_eq!(next_exit_epoch, VnEpoch(10));
1✔
1130

1131
            // Exit some nodes
1132
            store.exit(None, &nodes[0].public_key, VnEpoch(11)).unwrap();
1✔
1133
            store.exit(None, &nodes[1].public_key, VnEpoch(11)).unwrap();
1✔
1134
            store.exit(None, &nodes2[0].public_key, VnEpoch(12)).unwrap();
1✔
1135
            store.exit(None, &nodes2[1].public_key, VnEpoch(12)).unwrap();
1✔
1136
            store.exit(None, &nodes4[0].public_key, VnEpoch(13)).unwrap();
1✔
1137
            store.exit(None, &nodes4[1].public_key, VnEpoch(13)).unwrap();
1✔
1138
            store.exit(Some(&sid), &nodes3[0].public_key, VnEpoch(11)).unwrap();
1✔
1139
            store.exit(Some(&sid), &nodes3[1].public_key, VnEpoch(11)).unwrap();
1✔
1140

1141
            let next_exit_epoch = store.get_next_exit_epoch(None, VnEpoch(11), 2).unwrap();
1✔
1142
            assert_eq!(next_exit_epoch, VnEpoch(14));
1✔
1143

1144
            store.undo_exit(None, VnEpoch(11), &nodes4[0].public_key).unwrap();
1✔
1145
            assert!(
1✔
1146
                store
1✔
1147
                    .undo_exit(None, VnEpoch(11), &Default::default())
1✔
1148
                    .unwrap_err()
1✔
1149
                    .is_value_not_found()
1✔
1150
            );
1151
            assert!(
1✔
1152
                store
1✔
1153
                    .undo_exit(None, VnEpoch(110), &Default::default())
1✔
1154
                    .unwrap_err()
1✔
1155
                    .is_value_not_found()
1✔
1156
            );
1157

1158
            let next_exit_epoch = store.get_next_exit_epoch(None, VnEpoch(11), 2).unwrap();
1✔
1159
            assert_eq!(next_exit_epoch, VnEpoch(13));
1✔
1160
        }
1✔
1161

1162
        #[test]
1163
        fn it_allows_register_exit_register() {
1✔
1164
            let db = TempLmdbDatabase::with_dbs(DBS);
1✔
1165
            let txn = db.write_transaction();
1✔
1166
            let store = create_store(&db, &txn);
1✔
1167
            let nodes = insert_n_vns(&store, 1, 0, 3, None);
1✔
1168
            // Exit some nodes
1169
            store.exit(None, &nodes[0].public_key, VnEpoch(10)).unwrap();
1✔
1170

1171
            let public_key = nodes[0].public_key.clone();
1✔
1172
            let shard_key = make_hash2(public_key.as_bytes(), [1u8]);
1✔
1173
            let start_epoch = VnEpoch(15);
1✔
1174
            let entry = ValidatorNodeEntry {
1✔
1175
                public_key: public_key.clone(),
1✔
1176
                shard_key,
1✔
1177
                commitment: CompressedCommitment::from_compressed_key(new_public_key()),
1✔
1178
                activation_epoch: start_epoch,
1✔
1179
                sidechain_public_key: None,
1✔
1180
                ..Default::default()
1✔
1181
            };
1✔
1182
            store.insert(&entry).unwrap();
1✔
1183

1184
            assert!(store.is_vn_active(None, &public_key, VnEpoch(15)).unwrap());
1✔
1185

1186
            let next_exit_epoch = store.get_next_exit_epoch(None, VnEpoch(15), 2).unwrap();
1✔
1187
            assert_eq!(next_exit_epoch, VnEpoch(15));
1✔
1188
        }
1✔
1189
    }
1190

1191
    mod get_entire_vn_set {
1192
        use super::*;
1193

1194
        #[test]
1195
        fn it_returns_all_active_validators_at_given_epoch() {
1✔
1196
            let db = TempLmdbDatabase::with_dbs(DBS);
1✔
1197
            let txn = db.write_transaction();
1✔
1198
            let store = create_store(&db, &txn);
1✔
1199
            let nodes = insert_n_vns(&store, 1, 0, 3, None);
1✔
1200
            let nodes2 = insert_n_vns(&store, 10, 0, 4, None);
1✔
1201
            let sid = new_public_key();
1✔
1202
            let nodes3 = insert_n_vns(&store, 1, 0, 3, Some(&sid));
1✔
1203

1204
            // Exit some nodes
1205
            store.exit(None, &nodes[0].public_key, VnEpoch(11)).unwrap();
1✔
1206
            store.exit(None, &nodes[1].public_key, VnEpoch(11)).unwrap();
1✔
1207
            store.exit(None, &nodes2[0].public_key, VnEpoch(110)).unwrap();
1✔
1208
            store.exit(None, &nodes2[1].public_key, VnEpoch(110)).unwrap();
1✔
1209
            store.exit(Some(&sid), &nodes3[0].public_key, VnEpoch(11)).unwrap();
1✔
1210
            store.exit(Some(&sid), &nodes3[1].public_key, VnEpoch(11)).unwrap();
1✔
1211

1212
            let set = store.get_entire_vn_set(VnEpoch(10)).unwrap();
1✔
1213
            assert_eq!(set.len(), 10);
1✔
1214
            let set = store.get_entire_vn_set(VnEpoch(11)).unwrap();
1✔
1215
            assert_eq!(set.len(), 6);
1✔
1216
            let set = store.get_entire_vn_set(VnEpoch(110)).unwrap();
1✔
1217
            assert_eq!(set.len(), 4);
1✔
1218

1219
            // re-register
1220
            store
1✔
1221
                .insert(&ValidatorNodeEntry {
1✔
1222
                    shard_key: nodes2[0].shard_key,
1✔
1223
                    activation_epoch: VnEpoch(210),
1✔
1224
                    registration_epoch: VnEpoch(210),
1✔
1225
                    public_key: nodes2[0].public_key.clone(),
1✔
1226
                    commitment: CompressedCommitment::from_compressed_key(new_public_key()),
1✔
1227
                    sidechain_public_key: None,
1✔
1228
                    minimum_value_promise: Default::default(),
1✔
1229
                })
1✔
1230
                .unwrap();
1✔
1231

1232
            store
1✔
1233
                .insert(&ValidatorNodeEntry {
1✔
1234
                    shard_key: nodes2[1].shard_key,
1✔
1235
                    activation_epoch: VnEpoch(210),
1✔
1236
                    registration_epoch: VnEpoch(210),
1✔
1237
                    public_key: nodes2[1].public_key.clone(),
1✔
1238
                    commitment: CompressedCommitment::from_compressed_key(new_public_key()),
1✔
1239
                    sidechain_public_key: None,
1✔
1240
                    minimum_value_promise: Default::default(),
1✔
1241
                })
1✔
1242
                .unwrap();
1✔
1243
            // TODO: re-register with the same public key is not supported properly
1244
            // let set = store.get_entire_vn_set(VnEpoch(110)).unwrap();
1245
            // assert_eq!(set.len(), 4);
1246

1247
            // let set = store.get_entire_vn_set(VnEpoch(210)).unwrap();
1248
            // assert_eq!(set.len(), 6);
1249
        }
1✔
1250
    }
1251
}
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