Class ValidatorSet

A Height and PublicKey pair to represent expiring Validators

class ValidatorSet ;

Constructors

NameDescription
this (db, params) Constructor

Fields

NameTypeDescription
log LoggerLogger instance

Methods

NameDescription
add (height, finder, getPenaltyDeposit, enroll, pubkey) Add a enrollment data to the validators set
addPreimage (preimage) Add a pre-image information to a validator data
countActive (height) Gets the number of active validators at a given block height.
findRecentEnrollment (enroll_key, state) Find the most recent Enrollment with the provided UTXO hash, regardless of it's active status but order by status descending so that most recent is returned first
getEnrolledHeight (height, enroll_hash) In validatorSet DB, return the enrolled block height.
getEnrolledUTXOs (height, utxo_keys) Get all the current validators in ascending order with the utxokey including the slashed as the validator index requires them
getPreimage (enroll_key) Get validator's pre-image from the validator set
getPreimages (start_height) Get validators' pre-image information
getValidators (height) Get all the validators able to sign block at height
hasEnrollment (height, enroll_hash) Check if a enrollment data exists in the validator set.
hasPublicKey (height, pubkey) Check with public key if an enrollment data exists in the validator set
minEnrollmentHeight (height)
removeAll () Remove all validators from the validator set
slashValidator (enroll_hash, height) Slash the validator with the given UTXO key at the given height

Example

test for functions of ValidatorSet

import agora.consensus.data.Transaction;
import agora.consensus.EnrollmentManager;
import std.algorithm;
import std.conv;
import std.range;

const FirstEnrollHeight = Height(1);
scope storage = new MemoryUTXOSet;
auto getPenaltyDeposit = (Hash utxo)
{
    UTXO val;
    return storage.peekUTXO(utxo, val) ? 10_000.coins : 0.coins;
};
auto params = new immutable(ConsensusParams)();
scope set = new ValidatorSet(new ManagedDatabase(":memory:"), params);

Hash[] utxos;
genesisSpendable().take(8).enumerate
    .map!(en => en.value.refund(WK.Keys[en.index].address).sign(OutputType.Freeze))
    .each!((tx) {
        storage.put(tx);
        utxos ~= UTXO.getHash(tx.hashFull(), 0);
    });

// add enrollments
auto enroll = EnrollmentManager.makeEnrollment(utxos[0], WK.Keys[0], FirstEnrollHeight, params.ValidatorCycle);
assert(set.add(FirstEnrollHeight, &storage.peekUTXO, getPenaltyDeposit, enroll, WK.Keys[0].address) is null);
assert(set.countActive(FirstEnrollHeight) == 0);
assert(set.countActive(FirstEnrollHeight + 1) == 1);    // Will be active next block
ExpiringValidator[] ex_validators;
assert(set.hasEnrollment(FirstEnrollHeight, utxos[0]));
assert(set.add(FirstEnrollHeight, &storage.peekUTXO, getPenaltyDeposit, enroll, WK.Keys[0].address) == "Already enrolled at this height");

auto enroll2 = EnrollmentManager.makeEnrollment(utxos[1], WK.Keys[1], FirstEnrollHeight, params.ValidatorCycle);
assert(set.add(FirstEnrollHeight, &storage.peekUTXO, getPenaltyDeposit, enroll2, WK.Keys[1].address) is null);
assert(set.countActive(FirstEnrollHeight + 1) == 2);

const SecondEnrollHeight = Height(9);
auto enroll3 = EnrollmentManager.makeEnrollment(utxos[2], WK.Keys[2], SecondEnrollHeight, params.ValidatorCycle);
assert(set.add(SecondEnrollHeight, &storage.peekUTXO, getPenaltyDeposit, enroll3, WK.Keys[2].address) is null);
assert(set.countActive(SecondEnrollHeight + 1) == 3);

// check if enrolled heights are not set
Hash[] keys;
set.getEnrolledUTXOs(SecondEnrollHeight + 1, keys);
assert(keys.length == 3);
assert(keys.isStrictlyMonotonic!("a < b"));

// slash ValidatorSet
set.slashValidator(utxos[1], SecondEnrollHeight + 1);
assert(set.countActive(SecondEnrollHeight + 1) == 2);
assert(set.hasEnrollment(SecondEnrollHeight + 1,utxos[0]));
set.slashValidator(utxos[0], SecondEnrollHeight + 1);
// The enrollment will remain even though it is slashed
assert(set.hasEnrollment(SecondEnrollHeight + 1, utxos[0]));
set.removeAll();
assert(set.countActive(SecondEnrollHeight + 1) == 0);

Enrollment[] ordered_enrollments;
ordered_enrollments ~= enroll;
ordered_enrollments ~= enroll2;
/// PreImageCache for the first enrollment
auto cache = PreImageCycle(WK.Keys[0].secret, set.params.ValidatorCycle);

// Reverse ordering
ordered_enrollments.sort!("a.utxo_key > b.utxo_key");
foreach (i, ordered_enroll; ordered_enrollments)
    assert(set.add(FirstEnrollHeight, storage.getUTXOFinder(),
        getPenaltyDeposit, ordered_enroll, WK.Keys[i].address) is null);
set.getEnrolledUTXOs(FirstEnrollHeight + 1, keys);
assert(keys.length == 2);
assert(keys.isStrictlyMonotonic!("a < b"));

// test for adding and getting preimage
assert(set.getPreimage(utxos[0]) == PreImageInfo(enroll.utxo_key, enroll.commitment, FirstEnrollHeight));
assert(cache[FirstEnrollHeight] == enroll.commitment);
auto preimage_11 = PreImageInfo(utxos[0], cache[SecondEnrollHeight + 2], SecondEnrollHeight + 2);
assert(set.addPreimage(preimage_11));
assert(set.getPreimage(utxos[0]) == preimage_11);

auto far_height = Height(10000);
auto too_far_preimage = PreImageInfo(utxos[0], cache[far_height], far_height);
assert(!set.addPreimage(too_far_preimage));
assert(set.getPreimage(utxos[0]) == preimage_11);

// test for clear up expired validators
enroll = EnrollmentManager.makeEnrollment(utxos[2], WK.Keys[2], SecondEnrollHeight, params.ValidatorCycle);
assert(set.add(SecondEnrollHeight, &storage.peekUTXO, getPenaltyDeposit, enroll, WK.Keys[2].address) is null);
keys.length = 0;
assert(set.getEnrolledUTXOs(Height(set.params.ValidatorCycle + 8), keys));
assert(keys.length == 1);
assert(keys[0] == utxos[2]);

// add enrollment at the genesis block:
// validates blocks [1 .. ValidatorCycle] inclusively
assert(set.params.ValidatorCycle > 10);
set.removeAll();  // clear all

assert(set.countActive(SecondEnrollHeight + 1) == 0);
enroll = EnrollmentManager.makeEnrollment(utxos[0], WK.Keys[0], FirstEnrollHeight, params.ValidatorCycle);
assert(set.add(FirstEnrollHeight, &storage.peekUTXO, getPenaltyDeposit, enroll, WK.Keys[0].address) is null);

// still active at height 1008
keys.length = 0;

assert(set.countActive(Height(set.params.ValidatorCycle)) == 1);
assert(set.getEnrolledUTXOs(Height(set.params.ValidatorCycle), keys));
assert(keys.length == 1);
assert(keys[0] == utxos[0]);

// cleared after a new cycle was started (which started at height 1 so add 2)
assert(set.countActive(Height(set.params.ValidatorCycle + 2)) == 0);
assert(set.getEnrolledUTXOs(Height(1010), keys));
assert(keys.length == 0);
set.removeAll();  // clear all

enroll = EnrollmentManager.makeEnrollment(utxos[0], WK.Keys[0], FirstEnrollHeight, params.ValidatorCycle);
assert(set.add(FirstEnrollHeight, &storage.peekUTXO, getPenaltyDeposit, enroll, WK.Keys[0].address) is null);
far_height = Height(5 * params.ValidatorCycle);
foreach (idx; 1..7)
    assert(set.addPreimage(PreImageInfo(utxos[0], cache[Height(idx * params.ValidatorCycle)], Height(idx * params.ValidatorCycle))));
// enroll again at a much later height, preimage should not be updated
enroll = EnrollmentManager.makeEnrollment(utxos[0], WK.Keys[0], far_height, params.ValidatorCycle);
assert(set.getPreimage(utxos[0]).height > far_height);

enroll = EnrollmentManager.makeEnrollment(utxos[1], WK.Keys[1], FirstEnrollHeight, params.ValidatorCycle);
assert(set.add(FirstEnrollHeight, &storage.peekUTXO, getPenaltyDeposit, enroll, WK.Keys[1].address) is null);
// enroll again at a much later height, preimage should still be accepted
enroll = EnrollmentManager.makeEnrollment(utxos[1], WK.Keys[1], far_height, params.ValidatorCycle);
assert(set.add(far_height, &storage.peekUTXO, getPenaltyDeposit, enroll, WK.Keys[1].address) is null);
assert(set.getPreimage(utxos[1]).height == far_height);