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 thekeys
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:
- 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. - 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 mostMAX_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 toSTORE_PREFIX_REGISTERED_NAMES
and store key equal totrs.params.name
(serialized as a utf-8 encoded string) does not exist in the store. - Let
netId
be the hash digest ofSHA-256(trs.params.genesisBlockId || senderAddress)
where||
indicates bytes concatenation andsenderAddress
is the address of the user account corresponding to thetrs.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 toSTORE_PREFIX_NETWORK_ID
and store key equal totrs.params.netID
does not exist in the store.
- Then
- The
trs.params.initValidators.keys
array must contain at least 1 element (1 public key) and at mostMAX_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 astrs.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 thetrs.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
, wherenumberOfChains
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
whereinteropSidechainAcc
is the interoperability account object, serialized according to theinteroperabilityAccount
schema defined in Properties, serialization, and initial values of the interoperability module LIP, and initialized as:-
The
interopSidechainAcc.name
property as stated in thetrs.params.name
property. -
The
interopSidechainAcc.networkID
property is calculated as the output ofSHA-256(trs.params.genesisBlockId || senderAddress)
where||
indicates bytes concatenation andsenderAddress
is the address of the user account corresponding to thetrs.senderPublicKey
property. -
The content of the
interopSidechainAcc.validators
object is set as follows:- The
keys
array is equal to thetrs.params.initValidators.keys
array. - The
weights
array is equal to thetrs.params.initValidators.weights
array. - The
certificateThreshold
property is equal to thetrs.params.initValidators.certificateThreshold
property.
- The
-
The following registration CCM is added to
interopSidechainAcc.outbox
by calling theaddToOutbox(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 propertyinteropSidechainAcc.outbox.root
serialized according to theoutboxRoot
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 tochainID
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 tochainID
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 astrs.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
andtrs.params.signature
are validated as follows:-
The function
verifyWeightedAggSig(keyList, tag, netID, aggregationBits, signature, weights, certificateThreshold, message)
specified in the LIP 0038, must returnVALID
where:- The
keyList
property is an array containinggetValidatorAccount(address).blsKey
for every address incurrentValidators = getGeneratorList()
, wheregetValidatorAccount
andgetGeneratorList
are functions exposed by the validators module. - The
tag
is equal toTAG_CHAINREG_MESSAGE
. - The
netID
byte array corresponds to the network ID of the chain. - The
aggregationBits
argument is the byte array given intrs.params.aggregationBits
. - The
signature
argument is the aggregate signature given intrs.params.signature
. - The
weights
argument is equal to thecurrentValidatorsWeights
array , wherecurrentValidatorsWeights
is the second property of the object which is output of the functiongetValidatorBFTWeights
exposed by the BFT module. - The
certificateThreshold
argument is equal to the third output of the functiongetThresholds
exposed by the BFT module. - The
message
argument is the output bytes of the serialization, as specified in LIP 0027, oftrs.params.ownChainID
,trs.params.ownName
andtrs.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" ] }
- The
-
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
whereinteropMainchainAcc
is the interoperability account object, serialized according to theinteroperabilityAccount
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 thetrs.params.mainchainValidators.keys
array. - The
weights
array is equal to thetrs.params.mainchainValidators.weights
array. - The
certificateThreshold
property is equal to thetrs.params.mainchainValidators.certificateThreshold
property.
- The
- 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 propertyinteropMainchainAcc.outbox.root
serialized according to theoutboxRoot
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
whereownChain
is the chain account object, serialized according to theownChain
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.