Function isGenesisBlockInvalidReason

Check the validity of a genesis block

string isGenesisBlockInvalidReason (
  in ref const(Block) block
) nothrow @safe;

Follow the same rules as for Block except for the following: - Block height must be 0 - The previous block hash of the block must be empty - The block must contain at least 1 transaction - Transactions must have no input - Transactions must have at least one output - All the enrollments pass validation, which implies: - The enrollments refer to freeze tx's in this block - The signature for the Enrollment is valid

Parameters

NameDescription
block The genesis block to check

Returns

null if the genesis block is valid, otherwise a string explaining the reason it is invalid.

Example

Genesis block validation fail test

scope engine = new Engine();
import agora.serialization.Serializer;

Block block = GenesisBlock.serializeFull.deserializeFull!Block;
assert(block.isGenesisBlockValid());

scope fee_man = new FeeManager();
scope checker = &fee_man.check;
scope findGenesisEnrollments = getGenesisEnrollmentFinder();

// don't accept block height 0 from the network
block.header.height = 0;
block.assertValid!false(engine, Height(0), Hash.init, null,
    Enrollment.MinValidatorCount, checker, findGenesisEnrollments, toDelegate(utGetPenaltyDeposit));

// height check
block.header.height = 1;
assert(!block.isGenesisBlockValid());

block.header.height = 0;
assert(block.isGenesisBlockValid());

// .prev_block check
block.header.prev_block = block.header.hashFull();
assert(!block.isGenesisBlockValid());

block.header.prev_block = Hash.init;
assert(block.isGenesisBlockValid());

// enrollments length check
block.header.enrollments = null;
assert(!block.isGenesisBlockValid());

block = GenesisBlock.serializeFull.deserializeFull!Block;
assert(block.isGenesisBlockValid());

Transaction[] txs =
    GenesisBlock.txs.serializeFull.deserializeFull!(Transaction[]);

void checkValidity (in Block block)
{
    auto reason = block.isGenesisBlockInvalidReason();
    assert(reason is null, reason);
}

void buildMerkleTree (ref Block block, bool shouldSort = true)
{
    Hash[] merkle_tree;
    if (shouldSort) block.txs.sort;
    block.header.merkle_root =
        Block.buildMerkleTree(block.txs, merkle_tree);
}

Transaction makeNewTx ()
{
    Transaction new_tx = Transaction(
        [Output(Amount(100), KeyPair.random().address)]);
    return new_tx;
}

// Check consistency of `txs` field
{
    // Txs length check
    block.txs = null;
    assert(!block.isGenesisBlockValid());

    // at least 1 tx needed (todo: relax this?)
    block.txs ~= txs.filter!(tx => tx.isFreeze).array;
    buildMerkleTree(block);
    checkValidity(block);

    block = GenesisBlock.serializeFull.deserializeFull!Block;
    foreach (_; 0 .. 6)
        block.txs ~= makeNewTx();
    assert(block.txs.length == GenesisBlock.txs.length + 6);
    buildMerkleTree(block);
    checkValidity(block);

    block = GenesisBlock.serializeFull.deserializeFull!Block;
    // Txs sorting check
    block.txs.reverse;
    buildMerkleTree(block, false);
    assert(!block.isGenesisBlockValid());

    block.txs.reverse;
    buildMerkleTree(block, false);
    checkValidity(block);

    // there may be any number of txs, does not need to be power of 2
    block.txs ~= makeNewTx();
    buildMerkleTree(block);
    assert(block.txs.length == GenesisBlock.txs.length + 1);
    checkValidity(block);

    block = GenesisBlock.serializeFull.deserializeFull!Block;

    // Txs type check
    auto pre_type_change_txs = block.txs.dup;
    block.txs[0].outputs = [ Output(Amount(1), KeyPair.random().address, cast(OutputType)10) ];
    buildMerkleTree(block);
    assert(block.isGenesisBlockInvalidReason().canFind("Invalid enum value"));

    block.txs = pre_type_change_txs;
    buildMerkleTree(block);
    checkValidity(block);

    assert(block.txs.any!(tx => tx.isPayment));
    assert(block.txs.any!(tx => tx.isFreeze));

    // Input empty check
    block.txs[0].inputs ~= Input.init;
    buildMerkleTree(block);
    assert(!block.isGenesisBlockValid());

    block.txs = txs;
    buildMerkleTree(block);
    checkValidity(block);

    // Output not empty check
    block.txs[0].outputs = null;
    buildMerkleTree(block);
    assert(!block.isGenesisBlockValid());

    // disallow 0 amount
    Output zeroOutput =
        Output(Amount.invalid(0), WK.Keys[0].address);
    block.txs[0].outputs ~= zeroOutput;
    block.txs[0].outputs.sort;
    buildMerkleTree(block);
    assert(!block.isGenesisBlockValid());
}

block = GenesisBlock.serializeFull.deserializeFull!Block;

// enrollments validation test
Enrollment[] enrolls;
enrolls ~= Enrollment.init;
block.header.enrollments = enrolls;
assert(!block.isGenesisBlockValid());

block = GenesisBlock.serializeFull.deserializeFull!Block;

// modify the last hex byte of the merkle root
block.header.merkle_root[][$ - 1]++;
assert(!block.isGenesisBlockValid());

// now restore it back to what it was
block.header.merkle_root[][$ - 1]--;
checkValidity(block);
const last_root = block.header.merkle_root;

// the previous merkle root should not match the new txs
block.txs ~= makeNewTx();
block.header.merkle_root = last_root;
assert(!block.isGenesisBlockValid());

Example

Genesis block with transaction data is a test that fails validation

import agora.serialization.Serializer;

KeyPair key_pair = KeyPair.random;

scope fee_man = new FeeManager();
scope checker = &fee_man.check;

Block block = GenesisBlock.serializeFull.deserializeFull!Block;

// create data with nomal size
ubyte[] normal_data;
normal_data.length = fee_man.params.TxPayloadMaxSize;
foreach (idx; 0 .. normal_data.length)
    normal_data[idx] = cast(ubyte)(idx % 256);

// calculate fee
Amount normal_data_fee = calculateDataFee(normal_data.length,
    fee_man.params.TxPayloadFeeFactor);

// create a transaction with data payload and enough fee
Transaction dataTx = Transaction(null,
    [ Output(normal_data_fee, fee_man.params.CommonsBudgetAddress),
        Output(40_000.coins, key_pair.address)].sort.array,
    normal_data,
);

// add a new transaction with data payload to block
block.txs ~= dataTx;
block.txs.sort;

// build merkle tree
block.header.merkle_root =
    Block.buildMerkleTree(block.txs, block.merkle_tree);

assert(!block.isGenesisBlockValid(),
    "Genesis block should not have any transaction with data payload.");