Generating a genesis block

How to generate a genesis block for a blockchain application.

Prerequisites

To use this guide, ensure the the following criteria below has been fulfilled:

Each blockchain application needs a genesis block, which is the very first block of the blockchain.

If a module includes an account schema, it is necessary to update the genesis block after registering the module with the application, to update the accounts in the genesis block with the new properties.

The recommended and most convenient method to generate a new genesis block is Using the CLI to generate a new genesis block.

After the application bootstrap, the default devnet genesis block will be generated under /home/USERNAME/.lisk/APPNAME/config/default/.

If a new module that includes an account schema is then registered with the application, it will fail with an error similar to this:

$ ./bin/run start
Starting Lisk hello_app at /Users/mona/.lisk/hello_app.
12:31:44 INFO lisk-framework: Starting the app - hello_app (module=lisk:app)
12:31:44 INFO lisk-framework: If you experience any type of error, please open an issue on Lisk GitHub: https://github.com/LiskHQ/lisk-sdk/issues (module=lisk:app)
12:31:44 INFO lisk-framework: Contribution guidelines can be found at Lisk-sdk: https://github.com/LiskHQ/lisk-sdk/blob/development/docs/CONTRIBUTING.md (module=lisk:app)
12:31:44 INFO lisk-framework: Booting the application with Lisk Framework(0.1.0) (module=lisk:app)
12:31:44 INFO lisk-framework: Initializing controller (module=lisk:app)
12:31:44 INFO lisk-framework: Loading controller (module=lisk:app)
12:31:44 INFO lisk-framework: Event * was subscribed but not registered to the bus yet. (module=lisk:app)
    TypeError: Cannot read property 'helloMessage' of undefined
12:31:45 INFO lisk-framework: Application shutdown started (module=lisk:app)
{
 "errorCode": 2,
 "message": "process.exit"
}

To fix this error, it is required to update the genesis block of the application to include the newly created account schema.

Using the CLI to generate a new genesis block

The genesis block can be updated with the application CLI:

./bin/run genesis-block:create --output  /home/USERNAME/.lisk/hello_app/config/default
For more information about the genesis-block:create command, check the CLI command reference.

Now, wait until the new genesis block is generated.

Creating genesis block [===========         ] 55% 44.6s

After completion, the new genesis block will be copied to config/default/, where it automatically replaces the outdated genesis block.

Beside the new genesis-block.json, it will create two other files: forging_info.json and password.json. They can be used to update the forging information of the genesis delegates in the configuration of the blockchain application. This is a necessary requirement as each new generation of a genesis block will generate a new random set of genesis delegates.

Copy the following content to ~/.lisk/hello_app/config/default/config.json:

  • copy the content of forging_info.json to under forging.delegates

  • copy the content of password.json to under forging.defaultPassword

After the genesis block and config are updated, delete the data/ folder of the application, to reset the database:

rm -r ~/.lisk/hello_app/data/

The application should now start again successfully. To start it, run the following command:

Starting the application
./bin/run start

Manual genesis block creation (alternative with Lisk Genesis)

The genesis library can be used to generate a valid genesis block based on the above parameters. It can be imported directly from the lisk-sdk package, or alternatively it can be installed as a separate package @liskhq/lisk-genesis.

npm install lisk-sdk
# or
npm install @liskhq/lisk-genesis

Including the account asset schemas

It is necessary for the genesis block to include the account schemas of all modules that are registered with the application. This also includes all account schemas of the SDK default modules which are registered with the application by default.

To achieve this, first import all relevant modules to get their account schemas:

const { TokenModule, DPoSModule, KeysModule, SequenceModule, genesis, passphrase, cryptography, configDevnet } = require('lisk-sdk');
const { MyModule } = require('./my-module');

// Create the accountAssetSchemas
const token = new TokenModule(configDevnet.genesisConfig).accountSchema;
const dpos = new DPoSModule(configDevnet.genesisConfig).accountSchema;
const keys = new KeysModule(configDevnet.genesisConfig).accountSchema;
const sequence = new SequenceModule(configDevnet.genesisConfig).accountSchema;
const myModule = new MyModule().accountSchema;

// Add fieldNumber starting from 2. Field number 1 is assigned to address of the account
token.fieldNumber = 2;
dpos.fieldNumber = 3;
keys.fieldNumber = 4;
sequence.fieldNumber = 5;
myModule.fieldNumber = 6;

const accountAssetSchemas = {
  token,
  dpos,
  keys,
  sequence,
  myModule
};
The key of each account schema should equal the name property of the respective module.

The default modules are always required to be included in the application for it to function correctly.

If they are not included in the application, other modules need to be included which replace their functionalities.

Generating a list of genesis delegates

The genesis block includes a list of genesis delegates under the initDelegates key.

These delegates will forge during the bootstrap period of the blockchain, which lasts for 3 forging rounds (3 * 103 blocks = 309 blocks) by default.

The default length of the bootstrap period can be altered with the initRounds property.

Generating a fresh list of genesis delegates

If you don’t have a list of already existing account details, it is necessary to newly generate the accounts.

The three minimum properties for a delegate are listed below:

  • address(Buffer): Address of the delegate account.

  • token.balance(BigInt): Balance of the delegate.

  • dpos.delegate.username(string): Unique username of the delegate.

// Generating the genesis delegates

const newCredentials = () => {
    const pass = passphrase.Mnemonic.generateMnemonic();
    const keys = cryptography.getPrivateAndPublicKeyFromPassphrase(pass);
    const credentials = {
        address: cryptography.getBase32AddressFromPassphrase(pass),
        binaryAddress: cryptography.getAddressFromPassphrase(pass).toString("hex"),
        passphrase: pass,
        publicKey: keys.publicKey.toString("hex"),
        privateKey: keys.privateKey.toString("hex")
    };
    return credentials;
};

const credentials = [];


const newDelegate = (name) => {
  const cred = newCredentials();
  credentials.push(cred);
    const delegate = {
        address: Buffer.from(cred.binaryAddress, 'hex'),
        token: { balance: BigInt(100000000) },
        dpos: { delegate: { username: name } }
    };
    return delegate;
};

const generateDelegates = (amount) => {
  const delegates = [];
  const name = 'genesisDelegate';
  for (let i = 1; i <= amount; i++) {
    let nameNumber = name + i;
    delegates.push(newDelegate(nameNumber))
  }
  return delegates;
};

const delegates = generateDelegates(5);

Generating the list of genesis accounts

All accounts that exist already at the beginning of the network are listed under the property accounts of the genesis block.

The accounts property is a list of accounts that always needs to include the accounts for the genesis delegates, that were created in the previous step.

Besides this, any other accounts can be added here, and account properties such as balance can be configured as desired.

// Creating the genesis account list

const newAccount = () => {
  const cred = newCredentials();
  credentials.push(cred);
  const account = {
    address: Buffer.from(cred.binaryAddress, 'hex'),
    token: { balance: BigInt(25000000000) }
  };
  return account;
};

const generateAccounts = (amount) => {
  const accounts = [];
  for (let i = 1; i <= amount; i++) {
    accounts.push(newAccount())
  }
  return accounts;
};

const genesisAccounts = generateAccounts(3);

const accounts = [...delegates, ...genesisAccounts];

Creating the genesis block

As the final step, use the function createGenesisBlock() of the genesis library to generate the genesis block.

The parameters for the genesis block are all packed into one object.

  • initDelegates(Array): List of initial delegate addresses used during the bootstrap period to forge blocks. Addresses are expected to be in Buffer format.

  • accounts(Array): List of initial accounts in the network. The minimum required is address, however other properties such as balance can be included. Addresses are expected to be in Buffer format.

  • accountAssetSchemas(Object): The genesis block needs to contain all account asset schemas for all modules which are registered with the respective blockchain application. The different account asset schemas are all grouped together in one large object and added as accountAssetSchemas to the genesis block params. accountAssetSchemas is one of the most important parameters for generating a valid genesis block, so make sure it includes all required account asset schemas.

This object is provided as a parameter for the createGenesisBlock() function, which will then be used to generate the dedicated genesis block.

const genesisBlockParams = {
	initDelegates: delegates.map(a => a.address),
	accounts,
	accountAssetSchemas,
};

const genesisBlock = genesis.createGenesisBlock(genesisBlockParams);

console.log(genesisBlock);

Complete example

Full example: How to generate a genesis block
const { TokenModule, DPoSModule, KeysModule, SequenceModule, genesis, passphrase, cryptography, configDevnet } = require('lisk-sdk');
const { MyModule } = require('./my-module');

// Create the accountAssetSchemas
const token = new TokenModule(configDevnet.genesisConfig).accountSchema;
const dpos = new DPoSModule(configDevnet.genesisConfig).accountSchema;
const keys = new KeysModule(configDevnet.genesisConfig).accountSchema;
const sequence = new SequenceModule(configDevnet.genesisConfig).accountSchema;
const myModule = new MyModule().accountSchema;

// Add fieldNumber starting from 2. Field number 1 is assigned to address of the account
token.fieldNumber = 2;
dpos.fieldNumber = 3;
keys.fieldNumber = 4;
sequence.fieldNumber = 5;
myModule.fieldNumber = 6;

const accountAssetSchemas = {
  token,
  dpos,
  keys,
  sequence,
  myModule
};

// Generating the genesis delegates

const newCredentials = () => {
  const pass = passphrase.Mnemonic.generateMnemonic();
  const keys = cryptography.getPrivateAndPublicKeyFromPassphrase(pass);
  const credentials = {
    address: cryptography.getBase32AddressFromPassphrase(pass),
    binaryAddress: cryptography.getAddressFromPassphrase(pass).toString("hex"),
    passphrase: pass,
    publicKey: keys.publicKey.toString("hex"),
    privateKey: keys.privateKey.toString("hex")
  };
  return credentials;
};

const credentials = [];


const newDelegate = (name) => {
  const cred = newCredentials();
  credentials.push(cred);
  const delegate = {
    address: Buffer.from(cred.binaryAddress, 'hex'),
    token: { balance: BigInt(100000000) },
    dpos: { delegate: { username: name } }
  };
  return delegate;
};

const generateDelegates = (amount) => {
  const delegates = [];
  const name = 'genesisDelegate';
  for (let i = 1; i <= amount; i++) {
    let nameNumber = name + i;
    delegates.push(newDelegate(nameNumber))
  }
  return delegates;
};

const delegates = generateDelegates(5);

// Creating the genesis account list

const newAccount = () => {
  const cred = newCredentials();
  credentials.push(cred);
  const account = {
    address: Buffer.from(cred.binaryAddress, 'hex'),
    token: { balance: BigInt(25000000000) }
  };
  return account;
};

const generateAccounts = (amount) => {
  const accounts = [];
  for (let i = 1; i <= amount; i++) {
    accounts.push(newAccount())
  }
  return accounts;
};

const genesisAccounts = generateAccounts(3);

const accounts = [...delegates, ...genesisAccounts];

// Creating the genesis block

const genesisBlockParams = {
  initDelegates: delegates.map(a => a.address),
  accounts,
  accountAssetSchemas,
};

const genesisBlock = genesis.createGenesisBlock(genesisBlockParams);

console.log(genesisBlock);

The generated genesis block

The above script will create the following output:

Genesis block
{
  header: {
    generatorPublicKey: <Buffer >,
    height: 0,
    previousBlockID: <Buffer >,
    reward: 0n,
    signature: <Buffer >,
    timestamp: 1620398241,
    transactionRoot: <Buffer e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55>,
    version: 0,
    asset: { initRounds: 3, initDelegates: [Array], accounts: [Array] },
    id: <Buffer a8 eb 2c da 7b 2a 8f 0e 84 24 1e 78 af 78 4e c9 0b ce d0 39 d0 b4 98 87 95 a8 5d 63 83 be c5 a0>
  },
  payload: []
}

Optional parameters for the genesis block

Besides the three required properties initDelegates, accounts, and accountAssetSchemas, it is possible to set the following optional properties for the genesis block:

  • initRounds(number): Number of rounds for bootstrap period, default is 3.

  • height(number): Height of the genesis block.

  • timestamp(number): Timestamp of the genesis block.

  • previousBlockID(Buffer): Previous block ID. Can be used for example in case of regenesis.

Example: Generating a genesis block with all optional properties included
const genesisBlockParams = {
	initDelegates: delegates.map(a => a.address),
	accounts,
	accountAssetSchemas,
	initRounds: 200,
	height: 123,
	timestamp: 1520668243,
	previousBlockID: Buffer.from(
		'454690a1c37838326007519a7ce1c8a6a495df50898f1ebd69d22fbcedf9689a',
		'hex',
	)
};

const genesisBlock = genesis.createGenesisBlock(genesisBlockParams);

console.log(genesisBlock);