Function isInvalidReason

Check the validity of an enrollment.

string isInvalidReason (
  in ref const(Enrollment) enrollment,
  scope nothrow @safe bool delegate(in ref geod24.bitblob.BitBlob!(64L), out UTXO) findUTXO,
  in const(Height) height,
  scope nothrow @trusted bool delegate(in ref geod24.bitblob.BitBlob!(64L), out EnrollmentState) findEnrollment,
  scope nothrow @safe Amount delegate(geod24.bitblob.BitBlob!(64L)) getPenaltyDeposit
) nothrow @safe;

string isInvalidReason (
  in ref const(Enrollment) enrollment,
  scope nothrow @safe bool delegate(in ref geod24.bitblob.BitBlob!(64L), out UTXO) findUTXO,
  in const(Height) height,
  scope nothrow @trusted bool delegate(in ref geod24.bitblob.BitBlob!(64L), out EnrollmentState) findEnrollment,
  scope nothrow @safe Amount delegate(geod24.bitblob.BitBlob!(64L)) getPenaltyDeposit,
  out Amount stake
) nothrow @safe;

A Validator's enrollment is considered valid if: - UTXO is unspent frozen utxo - Signatures are authentic - The frozen amount must be equal to or greater than 40,000 BOA

Parameters

NameDescription
enrollment The enrollment of the target to be verified
findUTXO delegate to find the referenced unspent UTXOs with
height The Height that this Enrollment is proposed at
stake an output param for the staked Amount for this enroll

Returns

null if the validator's UTXO is valid, otherwise a string explaining the reason it is invalid.

Example

import std.algorithm.searching;
import std.string;
import agora.consensus.state.UTXOSet;

KeyPair[] key_pairs = [KeyPair.random, KeyPair.random, KeyPair.random, KeyPair.random];

Amount delegate(Hash utxo) @safe nothrow getPenaltyDeposit = (utxo) { return 10_000.coins; };

auto params = new immutable(ConsensusParams)();
auto stateDB = new ManagedDatabase(":memory:");
scope utxo_set = new UTXOSet(stateDB);
scope validator_set = new ValidatorSet(stateDB, params);
scope UTXOFinder utxoFinder = utxo_set.getUTXOFinder();

// normal frozen transaction
Transaction tx1 = Transaction(
    [Output(Amount.MinFreezeAmount, key_pairs[0].address, OutputType.Freeze)]
);

// payment transaction
Transaction tx2 = Transaction(
    [Output(Amount.MinFreezeAmount, key_pairs[1].address)]
);

// Insufficient freeze amount transaction
Transaction tx3 = Transaction(
    [Output(Amount(1), key_pairs[2].address, OutputType.Freeze)]
);

// normal freeze amount transaction
Transaction tx4 = Transaction(
    [Output(Amount.MinFreezeAmount, key_pairs[3].address)]
);

auto utxo_hash1 = UTXO.getHash(hashFull(tx1), 0);
auto utxo_hash2 = UTXO.getHash(hashFull(tx2), 0);
auto utxo_hash3 = UTXO.getHash(hashFull(tx3), 0);
auto utxo_hash4 = UTXO.getHash(hashFull(tx4), 0);

Pair signature_noise = Pair.random;

Pair node_key_pair_1 = Pair.fromScalar(key_pairs[0].secret);

Enrollment enroll1;
enroll1.utxo_key = utxo_hash1;
enroll1.commitment = hashFull(Scalar.random());
enroll1.enroll_sig = sign(node_key_pair_1, signature_noise, hashMulti(Height(0), enroll1));

Pair node_key_pair_2 = Pair.fromScalar(key_pairs[1].secret);

Enrollment enroll2;
enroll2.utxo_key = utxo_hash2;
enroll2.commitment = hashFull(Scalar.random());
enroll2.enroll_sig = sign(node_key_pair_2, signature_noise, hashMulti(Height(0), enroll2));

Pair node_key_pair_3 = Pair.fromScalar(key_pairs[2].secret);

Enrollment enroll3;
enroll3.utxo_key = utxo_hash3;
enroll3.commitment = hashFull(Scalar.random());
enroll3.enroll_sig = sign(node_key_pair_3, signature_noise, hashMulti(Height(0), enroll3));

// Make pair with non matching scalar and point
Pair node_key_pair_invalid = Pair(node_key_pair_2.v, node_key_pair_3.V);

Enrollment enroll4;
enroll4.utxo_key = utxo_hash4;
enroll4.commitment = hashFull(Scalar.random());
enroll4.enroll_sig = sign(node_key_pair_invalid, signature_noise, hashMulti(Height(0), enroll4));

assert(!enroll1.isValid(utxoFinder, Height(0),
                                &validator_set.findRecentEnrollment,
                                getPenaltyDeposit));
assert(!enroll2.isValid(utxoFinder, Height(0),
                                &validator_set.findRecentEnrollment,
                                getPenaltyDeposit));
assert(!enroll3.isValid(utxoFinder, Height(0),
                                &validator_set.findRecentEnrollment,
                                getPenaltyDeposit));
assert(!enroll4.isValid(utxoFinder, Height(0),
                                &validator_set.findRecentEnrollment,
                                getPenaltyDeposit));

utxo_set.updateUTXOCache(tx1, Height(0), params.CommonsBudgetAddress);
utxo_set.updateUTXOCache(tx2, Height(0), params.CommonsBudgetAddress);
utxo_set.updateUTXOCache(tx3, Height(0), params.CommonsBudgetAddress);
utxo_set.updateUTXOCache(tx4, Height(0), params.CommonsBudgetAddress);

// Nomal
assert(enroll1.isValid(utxoFinder, Height(0),
                                    &validator_set.findRecentEnrollment,
                                    getPenaltyDeposit));

// Unspent frozen UTXO not found for the validator.
assert(!enroll1.isValid( utxoFinder, Height(0),
                                    &validator_set.findRecentEnrollment,
                                    getPenaltyDeposit));

// UTXO is not frozen.
assert(canFind(enroll2.isInvalidReason(utxoFinder,
    Height(0), &validator_set.findRecentEnrollment, getPenaltyDeposit), "UTXO is not frozen"));

// The frozen amount must be equal to or greater than 40,000 BOA.
assert(!enroll3.isValid(utxoFinder, Height(0),
                                    &validator_set.findRecentEnrollment,
                                    getPenaltyDeposit));

// Enrollment signature verification has an error.
assert(!enroll4.isValid(utxoFinder, Height(0),
                                    &validator_set.findRecentEnrollment,
                                    getPenaltyDeposit));

const utxoPeek = &utxo_set.peekUTXO;
auto cycle = PreImageCycle(key_pairs[0].secret, params.ValidatorCycle);

enroll1.utxo_key = utxo_hash1;
enroll1.commitment = cycle[Height(0)];
enroll1.enroll_sig = sign(node_key_pair_1, signature_noise, hashMulti(Height(0), enroll1));

assert(validator_set.add(Height(0), utxoPeek, getPenaltyDeposit, enroll1,
                                            key_pairs[0].address) is null);

assert(validator_set.countActive(Height(params.ValidatorCycle + 1)) == 0);

// Add initial enrollment first
enroll1.commitment = cycle[Height(0)];
enroll1.enroll_sig = sign(node_key_pair_1, signature_noise, hashMulti(Height(0), enroll1));
validator_set.add(Height(0), utxoPeek, getPenaltyDeposit, enroll1, key_pairs[0].address);
// First 2 iterations should fail because commitment is wrong
foreach (offset; [-1, +1, 0])
{
    enroll1.commitment = cycle[Height(params.ValidatorCycle + offset)];
    enroll1.enroll_sig = sign(node_key_pair_1, signature_noise, hashMulti(Height(params.ValidatorCycle), enroll1));
    assert((offset == 0) == (validator_set.add(Height(params.ValidatorCycle),
                        utxoPeek, getPenaltyDeposit, enroll1, key_pairs[0].address) is null));
}
assert(validator_set.countActive(Height(params.ValidatorCycle + 1)) == 1);

Enrollment invalid;
assert(isInvalidReason(invalid,
    (in Hash, out UTXO utxo) { utxo.output.type = OutputType.Freeze; return true; },
    Height(0), null, null)
    == "Enrollment: Address is not a valid point on Curve25519");