Introduce chain registration mechanism

Hello everyone,

I would like to propose another LIP for the roadmap objective “Define sidechain registration and lifecycle”. This LIP specifies the sidechain and mainchain registration mechanism.

I’m looking forward to your feedback.

Here is the complete LIP draft:

LIP:
Title: Introduce chain registration mechanism
Author: Iker Alustiza <iker@lightcurve.io>
Type: Standards Track
Created: <YYYY-MM-DD>
Updated: <YYYY-MM-DD>
Requires: Add weights to Lisk-BFT consensus protocol, 0038, Properties, serialization, and initial values of the interoperability module, 
          Cross-chain messages

Abstract

This LIP introduces the concept of chain registration in the Lisk ecosystem.
The chain registration is a necessary step to make a sidechain interoperable with the Lisk mainchain.
Specifically, for the Lisk mainchain, this LIP specifies a new command for the sidechain registration.
This command creates a sidechain account in the Lisk mainchain with some specific properties given by the user submitting the transaction.
Similarly for sidechains, this LIP specifies the mainchain registration command.

Copyright

This LIP is licensed under the Creative Commons Zero 1.0 Universal.

Motivation

The Lisk ecosystem is a permissionless network of blockchains where a sidechain following the standard protocol can interoperate with any other Lisk compatible chain.
In particular, the Lisk mainchain serves as a central node or relayer for the entire ecosystem and every cross-chain interaction has to be sent through it.
This implies there should exist a standardized protocol for the Lisk mainchain to maintain a cross-chain channel to communicate with every sidechain.

The first step for establishing this cross-chain channel protocol is the chain registration process, which can be thought of as the creation/opening of the channel between two chains.
This process defines the data structures, and protocol rules that every chain needs to implement in the ecosystem if they want to interoperate with another specific chain.

For a general picture of the Lisk interoperability architecture, please refer to Properties, serialization, and initial values of the interoperability module LIP.

Rationale

In this LIP, the registration process introduced in the previous section is specified from the point of view of both the Lisk mainchain and sidechains.
For the Lisk mainchain, this is done by the sidechain registration command, whereas for sidechains, it is done by the mainchain registration command.

Sidechain Registration Process

As mentioned above, for a sidechain to be interoperable in the Lisk ecosystem, it has to be registered in the Lisk Mainchain via a sidechain registration command.
A transaction with this command can be sent by any user account in the Lisk Mainchain with enough funds to pay the required fee.
The processing of this command implies the creation of a sidechain account in the mainchain state associated with a unique network identifier and a name. This means that every new sidechain occupies a certain namespace in the ecosystem.
Additionally, every newly registered sidechain can increase the size of every cross-chain update command posted on the mainchain (due to the increasing size of the outboxRootWitness property of the command).
For these two reasons, the minimum fee for this command has an added constant similar to the extra fee in a delegate registration command.
The value of this extra registration fee is 10 LSK.

Once the sidechain registration command is processed, the sidechain account status is set to registered.
In this state, the cross-chain channel is still not active, so the users on the mainchain or other chains cannot send cross-chain messages (CCMs) to this sidechain yet.
Moreover, the liveness requirement to maintain the channel is not enforced, this means that there is no specific time requirement for a sidechain to be activated on the mainchain, it can stay in the registered status for any period of time.
When a first valid cross-chain update command from this sidechain is processed, the sidechain status is changed to active, making it active in the ecosystem.
Now it is possible to send CCMs to the sidechain and the liveness condition is enforced.

Sidechain Structures on Mainchain

As mentioned above, when a new sidechain is registered on the mainchain via a registration command, a sidechain account is created in the Lisk mainchain state.
Specifically, this implies that the corresponding key-value entries for the sidechain are added to the interoperability module store (see Figure 1).
The values of these entries are initialized as specified in Properties, serialization, and initial values of the interoperability module LIP, concretely the name, networkID, and initValidators properties are computed from the sidechain registration command.

This sidechain registration command also assigns a unique integer as the chain ID to the newly registered sidechain.
This chain ID integer is also part of the key of the interoperability store entries for the registered sidechain as shown in Figure 1.


Figure 1: Example of the interoperability store. For Lisk mainchain, the ‘own chain’ entry exists by default in the mainchain state whereas there is one entry per registered sidechain for the other four stores created by a sidechain registration command. For sidechains, the ‘own chain’ and mainchain account entries are unique in the state tree and are created by a mainchain registration command.

Sidechain Registration Command

The sidechain registration command contains the following parameters used to connect a new sidechain in the ecosystem.

name

The name property sets the name of the sidechain as a string of characters. It has to be unique in the ecosystem.

genesisBlockId

The ID of the genesis block (as defined in LIP0034).
It is computed from the SHA-256 digest of the serialized bytes of the sidechain genesis block.
It can also help future sidechain node operators to identify the sidechain genesis block with respect to its value.

initValidators

This property defines the set of eligible keys with their respective weights and the certificate threshold required to sign the first certificate from the sidechain.
It is an object containing the following properties:

  • keys: An array of BLS public keys.
    The set of public keys that are eligible to sign the next certificate.
  • weights: An array of integers of the same size as the keys property, where each element is the weight of the corresponding key.
    For DPoS chains, the value of the elements of this array is usually 1 as every active validator has the same finality weight to sign the next certificate.
  • certificateThreshold: An integer setting the minimum signatures weight required for the first sidechain certificate to be valid.

Chain ID

The chain ID uniquely identifies a chain in the Lisk ecosystem.
It serves a similar purpose for chains as addresses do for user accounts.
The chain ID for a sidechain is deterministically computed when processing the sidechain registration command.
Specifically, the chain ID of a new sidechain is assigned as an incremental integer similar to transaction nonces.
For example, if there are 41 chains already registered in the Lisk ecosystem (the mainchain and 40 sidechains), the next registered sidechain will have chainID = 42.
The chain ID has to be stated in every cross-chain interaction.
For example, it has to be specified in the receivingChainID property of a CCM to this sidechain and in the sendingChainId property of a cross-chain update command from this sidechain.

The format of chain IDs aims to provide an efficient and compact way to uniquely identify chains in the ecosystem.
This has another advantage in terms of usability: Users can easily remember the integer assigned as chain ID for their favorite blockchain applications.

Sidechain Network Identifier

The network identifier, or network ID, is a byte sequence unique to a chain that has to be prepended to the input of the signing function of every transaction, block, or message of the chain.
It is necessary to avoid transaction replays between different chains in the ecosystem.

In the Lisk ecosystem, the network ID for a sidechain is computed as the hash digest of the sidechain genesis block ID given in a transaction with the registration command and the address of the account sending this transaction.
This is convenient for two reasons:

  1. The sidechain developers can pre-compute the network ID of their sidechain as soon as they set the genesis block and an account to send the transaction on the mainchain.
    The sidechain can be started already from this moment without being registered on the mainchain.
  2. The network ID is known to the mainchain as soon as the sidechain is registered, thus it can validate cross-chain update commands coming from the sidechain without further context.

This LIP overwrites the network ID definition given in LIP 0009 and solves the open problem given in the Rationale section of that LIP.
Note that in the case of a sidechain undergoing a community hard fork, one of the competing forks will need to register their fork of the sidechain again on the Lisk Mainchain.
Regardless of the genesis block ID set by this sidechain fork, it has to be registered from a different user account which implies that the chain ID and network ID of the forked chain will be different from the original one.

Mainchain Registration on a Sidechain

Once the sidechain has been registered on the mainchain, a similar registration process should happen in the sidechain before the interoperable channel is opened between the two chains.
This is done by submitting a transaction with the mainchain registration command in the sidechain, which implies the creation of a mainchain account in the sidechain state associated with the Lisk mainchain and other structures needed for interoperability.
This mainchain account has a similar structure as the one depicted in Figure 1. By protocol, the chain ID of the mainchain is a constant equal to 1 in the ecosystem.
The network ID of the mainchain is also a constant since it has to be known to validate cross-chain update commands.

This registration process has to happen always after the sidechain registration on the mainchain since the sidechain has no prior knowledge of the current mainchain validators or its own chain ID and name.

Mainchain Registration Command

The mainchain registration command sets certain parameters in the sidechain related to the interoperability module and initializes the corresponding mainchain account.
This command requires the approval of the sidechain validators.
They have to agree on the content of this command and add their aggregated signatures accordingly.
It is important that the sidechain validators make sure that they are signing the registration command with the right information from the mainchain, otherwise, the sidechain interoperable functionality may be unusable.

This command has no requirement for a minimum fee since it should be submitted only once in a sidechain and approved by a majority of validators.
For this reason, a transaction with this command should be treated differently in terms of priority in case it is included in a sidechain node’s transaction pool.
The recommendation is that, once the transaction is properly signed by the validators and ready to be submitted, a validator simply includes it in its next forged block without including it in the transaction pool.
The command has the following parameters.

mainchainValidators

Similar to the initValidators object in the sidechain registration command, this property defines the set of mainchain validators with their respective weight and the certificate threshold required to sign the first certificate from the mainchain.

ownChainID

The chain ID assigned to this sidechain on the mainchain after processing the corresponding sidechain registration command.

ownName

The ownName property sets the name of the sidechain in its own state according to the name given in the mainchain. It has to be unique in the ecosystem.

Specification

Constants

Name Type Value Description
Interoperability Constants
MODULE_ID_INTEROPERABILITY uint32 64 ID of the interoperability module.
MAINCHAIN_ID bytes 1 Chain ID of the Lisk mainchain.
MAINCHAIN_NAME string “lisk-mainchain” Name of the Lisk mainchain.
MAINCHAIN_NETWORK_ID bytes TBD Network identifier of the Lisk mainchain.
Interoperability Store
STORE_PREFIX_OUTBOX_ROOT bytes 0x0000 Store prefix of the outbox root substore.
STORE_PREFIX_CHAIN_DATA bytes 0x8000 Store prefix of the chain data substore.
STORE_PREFIX_REGISTERED_NAMES bytes 0xe000 Store prefix of the chain names substore.
STORE_PREFIX_REGISTERED_NETWORK_IDS bytes 0xf000 Store prefix of the chain network IDs substore.
Interoperability Command IDs
COMMAND_ID_SIDECHAIN_REG uint32 0 Command ID of sidechain registration command.
COMMAND_ID_MAINCHAIN_REG uint32 1 Command ID of mainchain registration command.
CROSS_CHAIN_COMMAND_ID_REGISTRATION uint32 0 Cross-chain command ID of chain registration CCM.
General Constants
MIN_FEE_PER_BYTE uint64 1000 Minimum fee per byte.
REGISTRATION_FEE uint64 1000000000 Fee to pay for a sidechain registration command.
MAX_NUM_VALIDATORS uint32 199 Maximum number of validators in a sidechain.
MAX_LENGTH_NAME uint32 40 Maximum allowed name for a sidechain.
TAG_CHAINREG_MESSAGE bytes ASCII encoded string “LSK_CHAIN_REGISTRATION_”

Sidechain Registration Command

The command ID of this transaction is COMMAND_ID_SIDECHAIN_REG.

Fee

This command has an extra fee:

extra fee = REGISTRATION_FEE

where REGISTRATION_FEE is a constant given in the protocol.

Parameters

sidechainRegParams = {
  "type":"object",
  "properties":{
     "name":{
        "dataType":"string",
        "fieldNumber":1
     },
     "genesisBlockID":{
        "dataType":"bytes",
        "fieldNumber":2
     },
     "initValidators":{
        "type":"object",
        "properties":{
           "keys":{
              "type":"array",
              "items":{
                 "dataType":"bytes"
              },
              "fieldNumber":1
           },
           "weights":{
              "type":"array",
              "items":{
                 "dataType":"uint64"
              },
              "fieldNumber":2
           },
           "certificateThreshold":{
              "dataType":"uint64",
              "fieldNumber":3
           },
           "required":[
              "keys",
              "weights",
              "certificateThreshold"
           ],
           "fieldNumber":3
        }
     }
  },
  "required":[
     "name",
     "genesisBlockID",
     "initValidators"
  ]
}

Verification

Let trs be a transaction with module ID MODULE_ID_INTEROPERABILITY and command ID COMMAND_ID_SIDECHAIN_REG to be verfied.
Then the set of validity rules to validate trs.params are:

  • The trs.params.name property has to contain only characters from the set [a-z0-9!@$&_.] and has to be at most MAX_LENGTH_NAME characters long.
  • The trs.params.name property has to be unique with respect to the set of already registered sidechain names in the blockchain state, such that the entry with store prefix equal to STORE_PREFIX_REGISTERED_NAMES and store key equal to trs.params.name (serialized as a utf-8 encoded string) does not exist in the store.
  • Let netId be the hash digest of SHA-256(trs.params.genesisBlockId || senderAddress) where || indicates bytes concatenation and senderAddress is the address of the user account corresponding to the trs.senderPublicKey property:
    • Then netId has to be unique with respect to the set of already registered sidechain network IDs in the blockchain state.
      That is the entry with store prefix equal to STORE_PREFIX_NETWORK_ID and store key equal to trs.params.netID does not exist in the store.
  • The trs.params.initValidators.keys array must contain at least 1 element (1 public key) and at most MAX_NUM_VALIDATORS elements.
  • Every element in the trs.params.initValidators.keys array has to be unique.
  • Elements in the trs.params.initValidators.keys array have a length of 48 bytes.
  • The trs.params.initValidators.keys array must be ordered lexicographically.
  • The trs.params.initValidators.weights array must contain the same number of elements as trs.params.initValidators.keys.
  • Elements in the trs.params.initValidators.weights array are positive integers distinct from 0.
  • The range of valid values of the trs.params.initValidators.certificateThreshold property is given by the total sum of the validators weights:
    • Minimum value: ⌊ ⅓ × totalWeight

    • Maximum value: totalWeight

      where ⌊⋅⌋ is the floor function and totalWeight is the total sum of every element in the trs.params.initivalidators.weights array.

Execution

When a sidechain registration command is executed, new entries for the interoperability module are created in the mainchain state (see Figure 1).
In particular, let trs be a transaction with module ID MODULE_ID_INTEROPERABILITY and command ID COMMAND_ID_SIDECHAIN_REG to be executed.
Then:

  • The chain ID of the sidechain is assigned as chainID = numberOfChains + 1, where numberOfChains is the total number of registered chains on the Lisk mainchain (counting Lisk Mainchain).
  • An interoperability account store as specified in Properties, serialization, and initial values of the interoperability module LIP is created as:
    • storePrefix: STORE_PREFIX_CHAIN_DATA
    • storeKey: uint32be(chainID).
    • storeValue: interopSidechainAcc where interopSidechainAcc is the interoperability account object, serialized according to the interoperabilityAccount schema defined in Properties, serialization, and initial values of the interoperability module LIP, and initialized as:
      • The interopSidechainAcc.name property as stated in the trs.params.name property.

      • The interopSidechainAcc.networkID property is calculated as the output of SHA-256(trs.params.genesisBlockId || senderAddress) where || indicates bytes concatenation and senderAddress is the address of the user account corresponding to the trs.senderPublicKey property.

      • The content of the interopSidechainAcc.validators object is set as follows:

        • The keys array is equal to the trs.params.initValidators.keys array.
        • The weights array is equal to the trs.params.initValidators.weights array.
        • The certificateThreshold property is equal to the trs.params.initValidators.certificateThreshold property.
      • The following registration CCM is added to interopSidechainAcc.outbox by calling the addToOutbox(chainID, registrationCCM) logic as specified in Properties, serialization, and initial values of the interoperability module LIP:

        registrationCCM = {
        	"moduleID": MODULE_ID_INTEROPERABILITY,
        	"crossChainCommandID": CROSS_CHAIN_COMMAND_ID_REGISTRATION,
        	"index": 0,
        	"sendingChainID": MAINCHAIN_ID,
        	"receivingChainID": chainID,
        	"fee": 0,
        	"status": 0,
        	"params":{
        		"networkID": SHA-256(trs.params.genesisBlockId || senderAddress),
        		"name": trs.params.name,
        	}
        }
        
      • The rest of the properties, as specified in Interoperability module LIP, are initialized to their default values.

  • An outbox root store as specified in Properties, serialization, and initial values of the interoperability module LIP is created as:
    • storePrefix: STORE_PREFIX_OUTBOX_ROOT.
    • storeKey: uint32be(chainID).
    • storeValue: The property interopSidechainAcc.outbox.root serialized according to the outboxRoot schema defined in Properties, serialization, and initial values of the interoperability module LIP.
  • A name store as specified in Properties, serialization, and initial values of the interoperability module LIP is created as:
    • storePrefix: STORE_PREFIX_REGISTERED_NAMES.
    • storeKey: interopSidechainAcc.name serialized as a utf-8 encoded string.
    • storeValue: chainID serialized according to chainID schema defined in Properties, serialization, and initial values of the interoperability module LIP.
  • A network ID store as specified in Properties, serialization, and initial values of the interoperability module LIP is created as:
    • storePrefix: STORE_PREFIX_NETWORK_ID.
    • storeKey: interopSidechainAcc.networkID serialized as bytes.
    • storeValue: chainID serialized according to chainID schema defined in Properties, serialization, and initial values of the interoperability module LIP.

Mainchain Registration Command

The command ID is COMMAND_ID_MAINCHAIN_REG.

Fee

This command does not have an extra fee, i.e., extra fee = 0.

Parameters

mainchainRegParams = {
   "type":"object",
   "properties":{
      "ownChainID":{
         "dataType":"uint32",
         "fieldNumber":1
      },
      "ownName":{
         "dataType":"string",
         "fieldNumber":2
      },
      "mainchainValidators":{
         "type":"object",
         "properties":{
            "keys":{
               "type":"array",
               "items":{
                  "dataType":"bytes"
               },
               "fieldNumber":1
            },
            "weights":{
               "type":"array",
               "items":{
                  "dataType":"uint64"
               },
               "fieldNumber":2
            },
            "certificateThreshold":{
               "dataType":"uint64",
               "fieldNumber":3
            },
            "required":[
               "keys",
               "weights",
               "certificateThreshold"
            ],
            "fieldNumber":3
         }
      },
      "signature":{
         "dataType":"bytes",
         "fieldNumber":4
      },
      "aggregationBits":{
         "dataType":"bytes",
         "fieldNumber":5
      }
   },
   "required":[
      "ownChainID",
      "ownName",
      "mainchainValidators",
      "signature",
      "aggregationBits"
   ]
}

Verification

Let trs be a transaction with module ID MODULE_ID_INTEROPERABILITY and command ID COMMAND_ID_MAINCHAIN_REG to be verified.
The set of validity rules to validate trs.params are:

  • The trs.params.mainchainValidators.keys array must contain 101 elements (exact number of active delegates on Lisk mainchain.
  • Every element in the trs.params.mainchainValidators.keys array has to be unique.
  • Elements in the trs.params.mainchainValidators.keys array have a length of 48 bytes.
  • The trs.params.mainchainValidators.keys array must be ordered lexicographically.
  • The trs.params.mainchainValidators.weights array must contain the same number of elements as trs.params.mainchainValidators.keys.
  • The elements in the trs.params.mainchainValidators.weights array are integers with value equal to 1.
  • The value of the trs.params.mainchainValidators.certificateThreshold property has to be 68.
  • The properties trs.params.aggregationBits and trs.params.signature are validated as follows:
    • The function verifyWeightedAggSig(keyList, tag, netID, aggregationBits, signature, weights, certificateThreshold, message) specified in the LIP 0038, must return VALID where:

      • The keyList property is an array containing getValidatorAccount(address).blsKey for every address in currentValidators = getGeneratorList(), where getValidatorAccount and getGeneratorList are functions exposed by the validators module.
      • The tag is equal to TAG_CHAINREG_MESSAGE.
      • The netID byte array corresponds to the network ID of the chain.
      • The aggregationBits argument is the byte array given in trs.params.aggregationBits.
      • The signature argument is the aggregate signature given in trs.params.signature.
      • The weights argument is equal to the currentValidatorsWeights array , where currentValidatorsWeights is the second property of the object which is output of the function getValidatorBFTWeights exposed by the BFT module.
      • The certificateThreshold argument is equal to the third output of the function getThresholds exposed by the BFT module.
      • The message argument is the output bytes of the serialization, as specified in LIP 0027, of trs.params.ownChainID, trs.params.ownName and trs.params.mainchainValidators properties according to the following schema:
      registrationSignatureMessage = {  
         "type":"object",
         "properties":{
            "ownChainID":{
               "dataType":"uint32",
               "fieldNumber":1
            },
            "ownName":{
               "dataType":"string",
               "fieldNumber":2
            },
            "mainchainValidators":{
               "type":"object",
               "properties":{
                  "keys":{
                     "type":"array",
                     "items":{
                        "dataType":"bytes"
                     },
                     "fieldNumber":1
                  },
                  "weights":{
                     "type":"array",
                     "items":{
                        "dataType":"uint64"
                     },
                     "fieldNumber":2
                  },
                  "certificateThreshold":{
                     "dataType":"uint64",
                     "fieldNumber":3
                  },
                  "required":[
                     "keys",
                     "weights",
                     "certificateThreshold"
                  ],
                  "fieldNumber":3
               }
            }
         },
         "required":[
             "ownChainID",
             "ownName",
             "mainchainValidators"
         ]
       }
      

Execution

When a mainchain registration command is applied, new entries of the interoperability module are created in the sidechain state for the interoperability account, outbox root and own chain stores as specified in Properties, serialization, and initial values of the interoperability module LIP.
In particular, let trs be a transaction with module ID MODULE_ID_INTEROPERABILITY and command ID COMMAND_ID_MAINCHAIN_REG to be executed.
Then:

  • An interoperability account store as specified in Properties, serialization, and initial values of the interoperability module LIP is created as:
    • storePrefix: STORE_PREFIX_CHAIN_DATA.
    • storeKey: uint32be(MAINCHAIN_ID).
    • storeValue: interopMainchainAcc where interopMainchainAcc is the interoperability account object, serialized according to the interoperabilityAccount schema defined in Properties, serialization, and initial values of the interoperability module LIP, and initialized as:
      • interopMainchainAcc.name = MAINCHAIN_NAME.
      • interopMainchainAcc.networkID = MAINCHAIN_NETWORK_ID.
      • The content of the interopMainchainAcc.validators object is set as follows:
        • The keys array is equal to the trs.params.mainchainValidators.keys array.
        • The weights array is equal to the trs.params.mainchainValidators.weights array.
        • The certificateThreshold property is equal to the trs.params.mainchainValidators.certificateThreshold property.
      • The rest of the properties, as specified in Properties, serialization, and initial values of the interoperability module LIP, are initialized to their default values.
  • An outbox root store as specified in Properties, serialization, and initial values of the interoperability module LIP is created as:
    • storePrefix: STORE_PREFIX_OUTBOX_ROOT.
    • storeKey: uint32be(MAINCHAIN_ID).
    • storeValue: The property interopMainchainAcc.outbox.root serialized according to the outboxRoot schema defined in Properties, serialization, and initial values of the interoperability module LIP.
  • An own chain account store as specified in Properties, serialization, and initial values of the interoperability module LIP is created as:
    • storePrefix : STORE_PREFIX_CHAIN_DATA
    • storeKey: uint32be(0).
    • storeValue: ownChain where ownChain is the chain account object, serialized according to the ownChain schema defined in Properties, serialization, and initial values of the interoperability module LIP, and initialized as:
      • ownChain.ID = trs.params.ownChainID.
      • ownChain.name = trs.params.ownName.

Backwards Compatibility

This proposal, together with Properties, serialization, and initial values of the interoperability module LIP, Cross-chain updates LIP, Cross-chain messages LIP, and Sidechain recovery transactions LIP, is part of the interoperability module.
Chains adding this module will need to do so with a hardfork.

1 Like

This LIP has been updated to reflect a general change of terminology and to include the functions exposed by the new chain architecture modules.