Class EnrollmentManager
Handle enrollment data and manage the validators set
class EnrollmentManager
;
Constructors
Name | Description |
---|---|
this
(stateDB, cacheDB, config, params)
|
Constructor |
Fields
Name | Type | Description |
---|---|---|
enroll_pool
|
EnrollmentPool | Enrollment pool managing enrollments waiting to be a validator |
validator_set
|
ValidatorSet | Validator set managing validators' information such as Enrollment object enrolled height, and preimages. |
Methods
Name | Description |
---|---|
addEnrollment
(enroll, pubkey, height, finder, getPenaltyDeposit)
|
Add a enrollment data to the enrollment pool |
addValidator
(enroll, pubkey, height, finder, getPenaltyDeposit, self_utxos)
|
Add a validator to the validator set or update the enrolled height. |
createEnrollment
(utxo, height)
|
Build an Enrollment using makeEnrollment , stores and returns it
|
getCommitment
()
|
Get the commitment for the enrollment for this node |
getEnrolledUTXO
(height, finder)
|
|
getEnrollmentKey
()
|
Get the key for the enrollment for this node |
getEnrollmentPublicKey
()
|
Get the public key of node that is used for a enrollment |
getEnrollments
(height, peekUTXO, getPenaltyDeposit, findUTXO)
|
Get the unregistered enrollments that can be validator in the next block based on the current block height. |
getPreimage
(height)
|
Get a pre-image for revelation |
getValidatorPreimage
(utxo)
|
Get validator's pre-image from the validator set. |
getValidatorPreimages
(start_height)
|
Get validators' pre-image information |
isEnrolled
(height, finder)
|
|
isInvalidCandidateReason
(enroll, pubkey, height, findUTXO, getPenaltyDeposit)
|
Check if an enrollment is a valid candidate for the proposed height |
makeEnrollment
(utxo, key, seed, height, offset)
|
Build enrollment data for an arbitrary utxo + key combination |
removeEnrollment
(enroll_key)
|
Remove all enrollments associated with a key from pool |
Example
tests for member functions of EnrollmentManager
import agora .consensus .data .Transaction;
import std .algorithm;
import std .range;
scope utxo_set = new MemoryUTXOSet;
auto getPenaltyDeposit = (Hash utxo)
{
UTXO val;
return utxo_set .peekUTXO(utxo, val) ? 10_000 .coins : 0 .coins;
};
Hash[] utxo_hashes;
auto gen_key_pair = WK .Keys .Genesis;
KeyPair key_pair = WK .Keys .A;
// genesisSpendable returns 8 outputs
auto pairs = iota(8) .map!(idx => WK .Keys[idx]) .array;
genesisSpendable()
.enumerate
.map!(tup => tup .value
.refund(pairs[tup .index] .address)
.sign(OutputType .Freeze))
.each!((tx) {
utxo_set .put(tx);
utxo_hashes ~= UTXO .getHash(tx .hashFull(), 0);
});
auto utxos = utxo_set .storage;
// create an EnrollmentManager object
auto params = new immutable(ConsensusParams)();
auto man = new EnrollmentManager(key_pair, params);
// Useful constant
const EnrollAt1 = Height(1);
// check the return value of `getEnrollmentPublicKey`
assert(key_pair .address == man .getEnrollmentPublicKey());
// create and add the first Enrollment object
auto utxo_hash = utxo_hashes[0];
Enrollment[] enrolls_before = man .getEnrollments(EnrollAt1, &utxo_set .peekUTXO, getPenaltyDeposit);
assert(enrolls_before .length == 0);
Enrollment[] ordered_enrollments;
foreach (idx, kp; pairs[0 .. 3])
{
auto enroll = EnrollmentManager .makeEnrollment(utxo_hashes[idx], kp, EnrollAt1, params .ValidatorCycle);
assert(man .addEnrollment(enroll, kp .address, EnrollAt1, &utxo_set .peekUTXO, getPenaltyDeposit));
assert(man .enroll_pool .count() == idx + 1);
ordered_enrollments ~= enroll;
}
Enrollment[] enrolls = man .getEnrollments(EnrollAt1, &utxo_set .peekUTXO, getPenaltyDeposit);
assert(enrolls .length == 3);
assert(enrolls .isStrictlyMonotonic!("a.utxo_key < b.utxo_key"));
// get a stored Enrollment object
Enrollment stored_enroll;
assert((stored_enroll = man .enroll_pool .getEnrollment(utxo_hashes[1])) !=
Enrollment .init);
assert(stored_enroll == ordered_enrollments[1]);
// remove an Enrollment object
man .enroll_pool .remove(utxo_hashes[1]);
assert(man .enroll_pool .count() == 2);
// test for getEnrollment with removed enrollment
assert(man .enroll_pool .getEnrollment(utxo_hashes[1]) == Enrollment .init);
// test for enrollment block height update
const EnrollAt9 = Height(9);
assert(man .validator_set .countActive(EnrollAt9 + 1) == 0);
assert(man .validator_set .getEnrolledHeight(EnrollAt9, utxo_hash) == ulong .max);
// Add removed enrollment to the pool with the new height
auto enroll = EnrollmentManager .makeEnrollment(utxo_hashes[1], pairs[1], EnrollAt9, params .ValidatorCycle);
assert(man .addEnrollment(enroll, pairs[1] .address, EnrollAt9, &utxo_set .peekUTXO, getPenaltyDeposit));
// The expired enrollments in the pool from height 1 are cleared on this next call
// to get enrollments at a higher height. We do have an enrollment at height 9
assert(man .getEnrollments(EnrollAt9, &utxo_set .peekUTXO, getPenaltyDeposit) .length == 1);
assert(man .addValidator(enroll, pairs[1] .address, EnrollAt9, &utxo_set .peekUTXO, getPenaltyDeposit, utxos)
is null);
assert(man .validator_set .getEnrolledHeight(EnrollAt9 + 1, enroll .utxo_key) == EnrollAt9);
// One Enrollment was moved to validator set
assert(man .validator_set .countActive(EnrollAt9 + 1) == 1);
// Check last block of cycle is still active
assert(man .validator_set .countActive(EnrollAt9 + params .ValidatorCycle) == 1);
// Check block in next cycle is no longer active
assert(man .validator_set .countActive(EnrollAt9 + params .ValidatorCycle + 1) == 0);
// Pool should now be empty
assert(man .enroll_pool .count() == 0);
assert(man .getEnrollments(EnrollAt9, &utxo_set .peekUTXO, getPenaltyDeposit) .length == 0);
// clear up all validators
man .validator_set .removeAll();
ordered_enrollments .length = 0;
enroll = EnrollmentManager .makeEnrollment(utxo_hashes[0], pairs[0], Height(10), params .ValidatorCycle);
// A validator is enrolled at the height of 10.
PreImageInfo preimage;
assert(man .addValidator(enroll, WK .Keys[0] .address, Height(10),
&utxo_set .peekUTXO, getPenaltyDeposit, utxos) is null);
preimage = man .getPreimage(Height(12));
assert(preimage .height == Height(12));
assert(preimage .hash == man .cycle[preimage .height]);
// test for getting validators' UTXO keys
Hash[] keys;
enroll = EnrollmentManager .makeEnrollment(utxo_hashes[1], pairs[1], Height(11), params .ValidatorCycle);
// validator A with the `utxo_hash` and the enrolled height of 10.
// validator B with the 'utxo_hash2' and the enrolled height of 11.
// validator C with the 'utxo_hash3' and no enrolled height.
assert(man .addValidator(enroll, WK .Keys[1] .address, Height(11),
&utxo_set .peekUTXO, getPenaltyDeposit, utxos) is null);
assert(man .validator_set .countActive(Height(12)) == 2);
assert(man .validator_set .getEnrolledUTXOs(Height(12), keys));
assert(keys .length == 2);
enroll = EnrollmentManager .makeEnrollment(utxo_hashes[2], pairs[2], Height(12), params .ValidatorCycle);
// set an enrolled height for validator C
// set the block height to 1019, which means validator B is expired.
// there is only one validator in the middle of 1020th block being made.
assert(man .addValidator(
enroll, WK .Keys[2] .address, Height(12),
&utxo_set .peekUTXO, getPenaltyDeposit, utxos)
is null);
assert(man .validator_set .countActive(Height(params .ValidatorCycle + 12)) == 1);
assert(man .validator_set .getEnrolledUTXOs(Height(params .ValidatorCycle + 12), keys));
assert(keys .length == 1);
assert(keys[0] == enroll .utxo_key);
Example
tests for `ValidatorSet.countActive
import agora .consensus .data .Transaction;
import std .range;
scope utxo_set = new MemoryUTXOSet;
Hash[] utxo_hashes;
auto getPenaltyDeposit = (Hash utxo)
{
UTXO val;
return utxo_set .peekUTXO(utxo, val) ? 10_000 .coins : 0 .coins;
};
KeyPair key_pair = WK .Keys .A;
// genesisSpendable returns 8 outputs
auto pairs = iota(8) .map!(idx => WK .Keys[idx]) .array;
genesisSpendable()
.enumerate
.map!(tup => tup .value
.refund(pairs[tup .index] .address)
.sign(OutputType .Freeze))
.each!((tx) {
utxo_set .put(tx);
utxo_hashes ~= UTXO .getHash(tx .hashFull(), 0);
});
auto utxos = utxo_set .storage;
// create an EnrollmentManager object
auto params = new immutable(ConsensusParams)(20);
auto man = new EnrollmentManager(key_pair, params);
Height height = Height(2);
auto enroll = EnrollmentManager .makeEnrollment(utxo_hashes[0], WK .Keys[0], height, params .ValidatorCycle);
// create and add the first Enrollment object
assert(man .addEnrollment(enroll, WK .Keys[0] .address, height,
utxo_set .getUTXOFinder(), getPenaltyDeposit));
assert(man .validator_set .countActive(height + 1) == 0); // not active yet
assert(man .addValidator(enroll, WK .Keys[0] .address, height, &utxo_set .peekUTXO,
getPenaltyDeposit, utxos) is null);
assert(man .validator_set .countActive(height + 1) == 1); // updated
height = 3;
enroll = EnrollmentManager .makeEnrollment(utxo_hashes[1], WK .Keys[1], height, params .ValidatorCycle);
// create and add the second Enrollment object
assert(man .addEnrollment(enroll, WK .Keys[1] .address, height,
utxo_set .getUTXOFinder(), getPenaltyDeposit));
assert(man .validator_set .countActive(height + 1) == 1); // not active yet
assert(man .addValidator(enroll, WK .Keys[1] .address, height, &utxo_set .peekUTXO,
getPenaltyDeposit, utxos) is null);
assert(man .validator_set .countActive(height + 1) == 2); // updated
height = 4;
enroll = EnrollmentManager .makeEnrollment(utxo_hashes[2], WK .Keys[2], height, params .ValidatorCycle);
// create and add the third Enrollment object
assert(man .addEnrollment(enroll, WK .Keys[2] .address, height,
utxo_set .getUTXOFinder(), getPenaltyDeposit));
assert(man .validator_set .countActive(height + 1) == 2); // not active yet
assert(man .addValidator(enroll, WK .Keys[2] .address, height, &utxo_set .peekUTXO,
getPenaltyDeposit, utxos) is null);
assert(man .validator_set .countActive(height + 1) == 3); // updated
height = 5; // valid block height : 0 <= H < 20
assert(man .validator_set .countActive(height + 1) == 3); // not cleared yet
height = Height(1 + params .ValidatorCycle); // valid block height : 2 <= H < 22
assert(man .validator_set .countActive(height + 1) == 3);
height = Height(2 + params .ValidatorCycle); // valid block height : 3 <= H < 23
assert(man .validator_set .countActive(height + 1) == 2);
height = Height(3 + params .ValidatorCycle); // valid block height : 4 <= H < 24
assert(man .validator_set .countActive(height + 1) == 1);
height = Height(4 + params .ValidatorCycle); // valid block height : 5 <= H < 25
assert(man .validator_set .countActive(height + 1) == 0);
Example
Test for the height when the enrollment will be available
import agora .consensus .data .Transaction;
import agora .consensus .state .UTXOSet;
// create an EnrollmentManager
const validator_cycle = 20;
KeyPair key_pair = WK .Keys .A;
scope man = new EnrollmentManager(key_pair,
new immutable(ConsensusParams)(validator_cycle));
scope utxo_set = new UTXOSet(man .db);
auto getPenaltyDeposit = (Hash utxo)
{
UTXO val;
return utxo_set .peekUTXO(utxo, val) ? 10_000 .coins : 0 .coins;
};
genesisSpendable() .map!(txb => txb .refund(key_pair .address) .sign(OutputType .Freeze))
.each!(tx => utxo_set .updateUTXOCache(tx, Height(1), man .params .CommonsBudgetAddress));
auto utxos = utxo_set .getUTXOs(key_pair .address);
// create and add the first enrollment
Enrollment[] enrolls;
const firstEnrolledAt10 = Height(10);
auto enroll = man .createEnrollment(utxos .keys[0], firstEnrolledAt10);
assert(man .addEnrollment(enroll, key_pair .address, firstEnrolledAt10,
utxo_set .getUTXOFinder(), getPenaltyDeposit));
// if the current height is smaller than the available height,
// we can get no enrollment
enrolls = man .getEnrollments(Height(firstEnrolledAt10 - 1), &utxo_set .peekUTXO, getPenaltyDeposit);
assert(enrolls .length == 0);
// if the current height is equal to the available height we can get enrollments
enrolls = man .getEnrollments(firstEnrolledAt10, &utxo_set .peekUTXO, getPenaltyDeposit);
assert(enrolls .length == 1);
// if the current height is more than the available height we don't
enrolls = man .getEnrollments(firstEnrolledAt10 + 1, &utxo_set .peekUTXO, getPenaltyDeposit);
assert(enrolls .length == 0);
// make the enrollment a validator
man .addValidator(enroll, key_pair .address, firstEnrolledAt10, &utxo_set .peekUTXO, getPenaltyDeposit, utxos);
enrolls = man .getEnrollments(firstEnrolledAt10, &utxo_set .peekUTXO, getPenaltyDeposit);
assert(enrolls .length == 0);
// add the enrollment that is already a validator, and check if
// the enrollment can be nominated at the height before the cycle end
auto re_enroll = man .createEnrollment(utxos .keys[0], firstEnrolledAt10 + validator_cycle);
assert(man .addEnrollment(re_enroll, key_pair .address, firstEnrolledAt10 + validator_cycle, &utxo_set .peekUTXO,
getPenaltyDeposit));
// Can only enroll at exact height as the preimage is for that height
assert(man .getEnrollments(Height(firstEnrolledAt10 + validator_cycle - 1),
&utxo_set .peekUTXO, getPenaltyDeposit) .length == 0);
enrolls = man .getEnrollments(firstEnrolledAt10 + validator_cycle,
&utxo_set .peekUTXO, getPenaltyDeposit);
assert(enrolls .length == 1);
// We do this test after as the expired will be remved from the pool
assert(man .getEnrollments(firstEnrolledAt10 + validator_cycle + 1,
&utxo_set .peekUTXO, getPenaltyDeposit) .length == 0);
// make the enrollment a validator again
assert(man .addValidator(enroll, key_pair .address, firstEnrolledAt10 + validator_cycle,
&utxo_set .peekUTXO, getPenaltyDeposit, utxos));
// Enrollment now gone from the pool
assert(man .getEnrollments(firstEnrolledAt10 + validator_cycle,
&utxo_set .peekUTXO, getPenaltyDeposit) .length == 0);
Example
tests for adding enrollments from the same public key
import agora .consensus .data .Transaction;
import std .algorithm;
scope utxo_set = new MemoryUTXOSet;
auto getPenaltyDeposit = (Hash utxo)
{
UTXO val;
return utxo_set .peekUTXO(utxo, val) ? 10_000 .coins : 0 .coins;
};
KeyPair key_pair = WK .Keys .A;
genesisSpendable() .map!(txb => txb .refund(key_pair .address) .sign(OutputType .Freeze))
.each!(tx => utxo_set .put(tx));
Hash[] utxo_hashes = utxo_set .keys;
// create an EnrollmentManager object
auto params = new immutable(ConsensusParams)(10);
auto man = new EnrollmentManager(key_pair, params);
// check the return value of `getEnrollmentPublicKey`
assert(key_pair .address == man .getEnrollmentPublicKey());
// first enrollment succeeds
auto enroll = man .createEnrollment(utxo_hashes[0], Height(1));
assert(man .addEnrollment(enroll, key_pair .address, Height(1),
&utxo_set .peekUTXO, getPenaltyDeposit));
// adding first enrollment succeeds
assert(man .addValidator(enroll, key_pair .address, Height(1),
&utxo_set .peekUTXO, getPenaltyDeposit, utxo_set .storage) is null);
// second enrollment with the same public key fails
auto enroll2 = man .createEnrollment(utxo_hashes[1], Height(1));
assert(!man .addEnrollment(enroll2, key_pair .address, Height(1),
&utxo_set .peekUTXO, getPenaltyDeposit));
// adding second enrollment with the same public key fails
assert(man .addValidator(enroll2, key_pair .address, Height(1),
&utxo_set .peekUTXO, getPenaltyDeposit, utxo_set .storage) !is null);