Chain Registration

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: Chain Registration
Author: Iker Alustiza <iker@lightcurve.io>
Type: Standards Track
Created: <YYYY-MM-DD>
Updated: <YYYY-MM-DD>
Required: Add weights to Lisk-BFT consensus protocol LIP, BLS signatures LIP, Interoperability LIP, 
          Cross-chain messages LIP

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 transaction for the sidechain registration. This transaction 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 transaction.

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 Interoperability 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 transaction, whereas for sidechains, it is done by the mainchain registration transaction. Once both transactions have been processed, the registration process of the sidechain is completed and it is ready to interoperate in the ecosystem.

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 transaction. This transaction can be sent by any user account in the Lisk Mainchain with enough funds to pay the required fee. The processing of this transaction 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 transaction posted on the mainchain (due to the increasing size of the outboxRootWitness property of the transaction). For these two reasons, the minimum fee for this transaction has an added constant similar to the extra fee in a delegate registration transaction. The value of this extra registration fee is 10 LSK.

Once the sidechain registration transaction 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 transaction 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 transaction, 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 Interoperability LIP, concretely the name, networkID, and initValidators properties are computed from the sidechain registration transaction.

This sidechain registration transaction 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 a generic 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 transaction. For sidechains, the ‘own chain’ and mainchain account entries are unique in the state tree and are created by a mainchain registration transaction.

Sidechain Registration Transaction

The sidechain registration transaction contains the following properties 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 transaction. 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 transaction 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 the registration transaction 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 transactions 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 mainchain registration transaction 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 transactions.

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 Transaction

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

This transaction 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, this transaction has to 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 transaction asset has the following properties.

mainchainValidators

Similar to the initValidators object in the sidechain registration transaction, 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 transaction.

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
MODULE_ID_INTEROPERABILITY uint32 TBD
ASSET_ID_SIDECHAIN_REG uint32 0
ASSET_ID_MAINCHAIN_REG uint32 1
ASSET_ID_CCM_REGISTRATION uint32 7
MIN_FEE_PER_BYTE uint64 1000
REGISTRATION_FEE uint64 1000000000
MAX_NUM_VALIDATORS uint32 199
STORE_PREFIX_OUTBOX bytes 0x00 00
STORE_PREFIX_ACCOUNT bytes 0x80 00
STORE_PREFIX_NAME bytes 0xc0 00
STORE_PREFIX_NETWORK_ID bytes 0xe0 00
MAINCHAIN_ID uint32 1
MAINCHAIN_NET_ID bytes 0x6f201e72e20571b93ed42470caa94af1ace79dc9930ab5bb144ddd5df5753e73
MAINCHAIN_NAME string “lisk-mainchain”
MAX_LENGTH_NAME uint32 40
CHAINREG_MESSAGE_TAG bytes ASCII encoded string “LSK_CHAIN_REGISTRATION_”

Sidechain Registration Transaction

The value of the moduleID property is MODULE_ID_INTEROPERABILITY which corresponds to the interoperability module. The value of the assetID property is ASSET_ID_SIDECHAIN_REG.

Fee

The minimum fee of this transaction is:

minimum fee = REGISTRATION_FEE + MIN_FEE_PER_BYTE * size(trs)

where MIN_FEE_PER_BYTE and REGISTRATION_FEE are constants given in the protocol and size(trs) is the size in bytes of the serialized transaction trs.

Transaction Asset Schema

sidechainRegAsset = {
   "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"
   ]
}

Transaction Asset Validity

Let trs be the sidechain registration transaction to be validated. Then the set of validity rules to validate trs.asset are:

  • The trs.asset.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.asset.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 storage prefix equal to STORE_PREFIX_NAME and storage key equal to trs.asset.name (serialized as a utf-8 encoded string) does not exist in the store.
  • Let netId be the hash digest of SHA-256(trs.asset.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 storage prefix equal to STORE_PREFIX_NETWORK_ID and storage key equal to trs.asset.netID does not exist in the store.
  • The trs.asset.initValidators.keys array must contain at least 1 element (1 public key) and at most MAX_NUM_VALIDATORS elements.
  • Every element in the trs.asset.initValidators.keys array has to be unique.
  • Elements in the trs.asset.initValidators.keys array have a length of 48 bytes.
  • The trs.asset.initValidators.keys array must be ordered lexicographically.
  • The trs.asset.initValidators.weights array must contain the same number of elements as trs.asset.initValidators.keys.
  • Elements in the trs.asset.initValidators.weights array are positive integers distinct from 0.
  • The range of valid values of the trs.asset.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.asset.initivalidators.weights array.

Transaction Application

When a sidechain registration transaction is applied, new entries for the interoperability module are created in the mainchain state (see Figure 1). In particular, let trs be the sidechain registration transaction to be processed, 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 the Properties, serialization, and initial values of the interoperability module LIP is created as:
    • storagePrefix: STORE_PREFIX_ACCOUNT
    • storageKey: uint32be(chainID).
    • storageValue: interopSidechainAcc where interopSidechainAcc is the interoperability account object, serialized according to the interoperabilityAccount schema, and initialized as:
      • The interopSidechainAcc.name property as stated in the trs.asset.name property.

      • The interopSidechainAcc.networkID property is calculated as the output of SHA-256(trs.asset.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.asset.initValidators.keys array.
        • The weights array is equal to the trs.asset.initValidators.weights array.
        • The certificateThreshold property is equal to the trs.asset.initValidators.certificateThreshold property.
      • The following registration CCM is added to interopSidechainAcc.outbox by calling the addToOutbox(chainID, registrationCCM) logic:

        registrationCCM = {
        	"moduleID": MODULE_ID_INTEROPERABILITY,
        	"assetID": ASSET_ID_CCM_REGISTRATION,
        	"index": 0,
        	"sendingChainID": MAINCHAIN_ID,
        	"receivingChainID": chainID,
        	"fee": 0,
        	"status": 0,
        	"asset":{
        		"networkID": SHA-256(trs.asset.genesisBlockId || senderAddress),
        		"name": trs.asset.name,
        	}
        }
        
      • The rest of the properties, as specified in the Properties, serialization, and initial values of the interoperability module LIP, are initialized to their default values.

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

Mainchain Registration Transaction

The value of the moduleID property is MODULE_ID_INTEROPERABILITY. The value of the assetID property is ASSET_ID_MAINCHAIN_REG.

Fee

The minimum fee of this transaction is:

minimum fee = 0.

Transaction Asset Schema

mainchainRegAsset = {
   "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"
   ]
}

Transaction Asset Validity

The set of validity rules to validate trs.asset are:

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

      • The keyList argument is an array with the BLS public keys of the current validators of the chain.
      • The tag is equal to CHAINREG_MESSAGE_TAG.
      • The netID byte array corresponds to the network ID of the chain.
      • The aggregationBits argument is the byte array given in trs.asset.aggregationBits.
      • The signature argument is the aggregate signature given in trs.asset.signature.
      • The weights argument is the weights of the current validators of the chain.
      • The certificateThreshold argument is the current certificate threshold of the chain.
      • The message argument is the output bytes of the serialization, as specified in LIP 0027, of trs.asset.ownChainID, trs.asset.ownName and trs.asset.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"
         ]
       }
      

Transaction Application

When a mainchain registration transaction 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 the Properties, serialization, and initial values of the interoperability module LIP. In particular, let trs be the mainchain registration transaction to be processed, then:

  • An interoperability account store as specified in the Properties, serialization, and initial values of the interoperability module LIP is created as:
    • storagePrefix: STORE_PREFIX_ACCOUNT.
    • storageKey: uint32be(MAINCHAIN_ID).
    • storageValue: interopMainchainAcc where interopMainchainAcc is the interoperability account object, serialized according to the interoperabilityAccount schema and initialized as:
      • interopMainchainAcc.name = MAINCHAIN_NAME.
      • interopMainchainAcc.networkID = MAINCHAIN_NET_ID.
      • The content of the interopMainchainAcc.validators object is set as follows:
        • The keys array is equal to the trs.asset.mainchainValidators.keys array.
        • The weights array is equal to the trs.asset.mainchainValidators.weights array.
        • The certificateThreshold property is equal to the trs.asset.mainchainValidators.certificateThreshold property.
      • The rest of the properties are initialized to their default values.
  • An outbox root store as specified in the Properties, serialization, and initial values of the interoperability module LIP is created as:
    • storagePrefix: STORE_PREFIX_OUTBOX.
    • storageKey: uint32be(MAINCHAIN_ID).
    • storageValue: The property interopMainchainAcc.outbox.root serialized according to the outboxRoot schema.
  • An own chain account store as specified in the Properties, serialization, and initial values of the interoperability module LIP is created as:
    • storagePrefix : STORE_PREFIX_ACCOUNT
    • storageKey: uint32be(0).
    • storageValue: ownChain where ownChain is the chain account object, serialized according to the ownChain schema, and initialized as:
      • ownChain.ID = trs.asset.ownChainID.
      • ownChain.name = trs.asset.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