Function isGenesisBlockInvalidReason
Check the validity of a genesis block
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
Name | Description |
---|---|
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.");