From 97deedb40fa1b4adad47326817429364aa8bbcaf Mon Sep 17 00:00:00 2001 From: Sergey Shemyakov Date: Fri, 10 Feb 2023 15:58:34 +0100 Subject: [PATCH 01/12] Changed genesis asset schema, verification and genesis state initialization --- proposals/lip-0045.md | 224 +++++++++++++----------------------------- 1 file changed, 68 insertions(+), 156 deletions(-) diff --git a/proposals/lip-0045.md b/proposals/lip-0045.md index e9eadaa21..b76ce145c 100644 --- a/proposals/lip-0045.md +++ b/proposals/lip-0045.md @@ -1384,187 +1384,58 @@ def isChainIDAvailable(chainID: ChainID) -> bool: ### Genesis Block Processing -#### Genesis State Initialization - -During the genesis state initialization stage of a genesis block `g`, the following steps are executed. If any step fails, the block is discarded and has no further effect. - -Let `genesisBlockAssetBytes` be the `data` bytes included in the genesis block assets for the Interoperability module and let `interoperabilityAsset = decode(genesisInteroperabilityStoreSchema, genesisBlockAssetBytes)`. Each top level entry in the schema corresponds to the substore of the Interoperability module store with the same name. - -* Within each substore, check that all entries have a different `storeKey`. -* For each entry in the chain data substore there must be a corresponding entry (with the same value of `storeKey`) in the outbox root, channel data, and chain validators substores and viceversa. However, if the chain account has status set to terminated, the entry in the outbox root must *not* be present. -* For each entry in the terminated outbox substore there must be a corresponding entry (with the same value of `storeKey`) in the terminated state substore. -* For each entry in the terminated state substore, if the property `initialized` is set to `False`, the corresponding entry in the terminated outbox substore must *not* be present. Furthermore, the `stateRoot` must be set to `EMPTY_HASH` and `mainchainStateRoot` must not be set to `EMPTY_HASH`. Conversely, if the property `initialized` is set to `True`, the `stateRoot` must not be set to `EMPTY_HASH` and `mainchainStateRoot` must be set to `EMPTY_HASH`. -* For each entry `chainValidators` in `interoperabilityAsset.chainValidatorsSubstore`, let `activeValidators = chainValidators.storeValue.activeValidators` and let `certificateThreshold = chainValidators.storeValue.certificateThreshold`: - * `activeValidators` must have at least 1 element and at most `MAX_NUM_VALIDATORS` elements. - * `activeValidators` must be ordered lexicographically by `blsKey` property. - * All `blsKey` properties must have length `BLS_PUBLIC_KEY_LENGTH` and must be pairwise distinct. - * The `bftWeight` property of each element must be positive integer. - * Let `totalWeight` be the sum of the `bftWeight` property of every element in `activeValidators`. Then `totalWeight` has to be less than or equal to `MAX_UINT64`. - * Check that `totalWeight//3 + 1 <= certificateThreshold <= totalWeight`, where `//` indicates integer division. - * Check that the corresponding `validatorsHash` stored in `chainAccount.lastCertificate.validatorsHash` matches with the value computed from `activeValidators` and `certificateThreshold`. -* On a sidechain, if a chain account for another sidechain is present (`storeKey != getMainchainID()`), then a chain account for the mainchain (`storeKey == getMainchainID()`) must be present, as well as the own chain account. -* For each entry `chainData` in `interoperabilityAsset.chainDataSubstore`: - * Check that `chainData.storeKey[0] == ownChainAccount.chainID[0]` and `chainData.storeKey != ownChainAccount.chainID`. - * Check that `chainData.lastCertificate.timestamp < g.header.timestamp. -* For each entry `channelData` in `interoperabilityAsset.channelDataSubstore`, let `channel = channelData.storeValue`: - * Either `channel.messageFeeTokenID==Token.getTokenIDLSK()` (corresponding to the LSK token), or `Token.getChainID(channel.messageFeeTokenID)` must be equal to `channelData.storeKey` or `ownChainAccount.chainID` (the message fee token must be a native token of either chains). -* On a sidechain, `interoperabilityAsset.registeredNamesSubstore` must be empty. -* For every substore, create all the corresponding entries in the Interoperability module state. +#### Genesis Assets Schema ```java genesisInteroperabilityStoreSchema = { "type": "object", "required": [ - "outboxRootSubstore", - "chainDataSubstore", - "channelDataSubstore", - "chainValidatorsSubstore", - "ownChainDataSubstore", - "terminatedStateSubstore", - "terminatedOutboxSubstore", - "registeredNamesSubstore" + "ownChainInfo", + "chainInfos" ], "properties": { - "outboxRootSubstore": { - "type": "array", - "fieldNumber": 1, - "items": { - "type": "object", - "required": ["storeKey", "storeValue"], - "properties": { - "storeKey": { - "dataType": "bytes", - "fieldNumber": 1 - }, - "storeValue": { - ...outboxRootSchema, - "fieldNumber": 2, - } - } - } + "ownChainInfo": { + ...ownChainAccountSchema, + "fieldNumber": 1 }, - "chainDataSubstore": { + "chainInfos": { "type": "array", "fieldNumber": 2, "items": { "type": "object", - "required": ["storeKey", "storeValue"], + "required": [ + "chainID", + "chainData", + "channelData", + "chainValidators", + "terminatedState", + "terminatedOutbox" + ], "properties": { - "storeKey": { + "chainID": { "dataType": "bytes", + "length": CHAIN_ID_LENGTH, "fieldNumber": 1 }, - "storeValue": { + "chainData": { ...chainDataSchema, - "fieldNumber": 2, - } - } - } - }, - "channelDataSubstore": { - "type": "array", - "fieldNumber": 3, - "items": { - "type": "object", - "required": ["storeKey", "storeValue"], - "properties": { - "storeKey": { - "dataType": "bytes", - "fieldNumber": 1 + "fieldNumber": 2 }, - "storeValue": { + "channelData": { ...channelDataSchema, - "fieldNumber": 2, - } - } - } - }, - "chainValidatorsSubstore": { - "type": "array", - "fieldNumber": 4, - "items": { - "type": "object", - "required": ["storeKey", "storeValue"], - "properties": { - "storeKey": { - "dataType": "bytes", - "fieldNumber": 1 + "fieldNumber": 3 }, - "storeValue": { + "chainValidators": { ...chainValidatorsSchema, - "fieldNumber": 2, - } - } - } - }, - "ownChainDataSubstore": { - "type": "array", - "fieldNumber": 5, - "items": { - "type": "object", - "required": ["storeKey", "storeValue"], - "properties": { - "storeKey": { - "dataType": "bytes", - "fieldNumber": 1 + "fieldNumber": 4 }, - "storeValue": { - ...ownChainAccountSchema, - "fieldNumber": 2, - } - } - } - }, - "terminatedStateSubstore": { - "type": "array", - "fieldNumber": 6, - "items": { - "type": "object", - "required": ["storeKey", "storeValue"], - "properties": { - "storeKey": { - "dataType": "bytes", - "fieldNumber": 1 - }, - "storeValue": { + "terminatedState": { ...terminatedStateAccountSchema, - "fieldNumber": 2, - } - } - } - }, - "terminatedOutboxSubstore": { - "type": "array", - "fieldNumber": 7, - "items": { - "type": "object", - "required": ["storeKey", "storeValue"], - "properties": { - "storeKey": { - "dataType": "bytes", - "fieldNumber": 1 + "fieldNumber": 5 }, - "storeValue": { + "terminatedOutbox": { ...terminatedOutboxAccountSchema, - "fieldNumber": 2, - } - } - } - }, - "registeredNamesSubstore": { - "type": "array", - "fieldNumber": 8, - "items": { - "type": "object", - "required": ["storeKey", "storeValue"], - "properties": { - "storeKey": { - "dataType": "bytes", - "fieldNumber": 1 - }, - "storeValue": { - ...registeredNamesSchema, - "fieldNumber": 2, + "fieldNumber": 6 } } } @@ -1575,6 +1446,47 @@ genesisInteroperabilityStoreSchema = { Here, the `...` notation, borrowed from [JavaScript ES6 data destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring), indicates that the corresponding schema should be inserted in place, and it is just used for notational convenience. +#### Genesis State Initialization + +During the genesis state initialization stage of a genesis block `g`, the following steps are executed. If any step fails, the block is discarded and has no further effect. + +Let `genesisBlockAssetBytes` be the `data` bytes included in the genesis block assets for the Interoperability module and let `interoperabilityAsset = decode(genesisInteroperabilityStoreSchema, genesisBlockAssetBytes)`. Let `chainInfos = interoperabilityAsset.chainInfos`. + +##### Genesis Asset Verification + +* Within `chainInfos`, check that all entries have a different `chainID`. +* For every `chainInfo` in `chainInfos`, properties `chainInfo.chainData`, `chainInfo.channelData` and `chainInfo.chainValidators` must be non-empty. +* For every `chainInfo` in `chainInfos` with non-empty `chainInfo.terminatedOutbox`, the field `chainInfo.terminatedState` must also be non-empty. +* For every `chainInfo` in `chainInfos` with `chainInfo.terminatedState.initialized == False`, the field `chainInfo.terminatedOutbox` must be empty, `chainInfo.terminatedState.stateRoot` must be equal to `EMPTY_HASH` and `chainInfo.terminatedState.mainchainStateRoot` must not be equal to `EMPTY_HASH`. Conversely, if `chainInfo.terminatedState.initialized == True`, then `chainInfo.terminatedState.stateRoot` must not be equal to `EMPTY_HASH` and `chainInfo.terminatedState.mainchainStateRoot` must be equal to `EMPTY_HASH`. todo: must the terminated outbox be non-empty in this case? +* For each entry `chainInfo` in `chainInfos`, let `chainData = chainInfo.chainData`, then check: + * all `chainData.name` are unique. + * all `chainData.name` have character set `a-z0-9!@$&_.`. + * property `chainData.status` is in set `{CHAIN_STATUS_REGISTERED, CHAIN_STATUS_ACTIVE, CHAIN_STATUS_TERMINATED}`. +* For each entry `chainInfo` in `chainInfos`, let `activeValidators = chainInfo.chainValidators.activeValidators` and let `certificateThreshold = chainInfo.chainValidators.certificateThreshold`, then check: + * `activeValidators` must have at least 1 element and at most `MAX_NUM_VALIDATORS` elements. + * `activeValidators` must be ordered lexicographically by `blsKey` property. + * All `blsKey` properties must be pairwise distinct. + * Let `totalWeight` be the sum of the `bftWeight` property of every element in `activeValidators`. Then `totalWeight` has to be less than or equal to `MAX_UINT64`. + * Check that `totalWeight//3 + 1 <= certificateThreshold <= totalWeight`, where `//` indicates integer division. + * Check that the corresponding `validatorsHash` stored in `chainInfo.chainData.lastCertificate.validatorsHash` matches with the value computed from `activeValidators` and `certificateThreshold`. +* On a sidechain, if `chainInfos` has an entry for another sidechain (`chainID != getMainchainID()`), then there must also exist an entry for the mainchain (`chainID == getMainchainID`) and `interoperabilityAsset.ownChainInfo` must be non-empty. +* For each entry `chainInfo` in `chainInfos`, check that `chainInfo.chainData.lastCertificate.timestamp < g.header.timestamp`. +* For each entry `chainInfo` in `chainInfos`, check that `chainInfo.chainId[0] == interoperabilityAsset.ownChainInfo.chainID[0]` and `chainInfo.chainId != interoperabilityAsset.ownChainInfo.chainID`. +* On the mainchain, check that `interoperabilityAsset.ownChainInfo.name == CHAIN_NAME_MAINCHAIN` and `interoperabilityAsset.ownChainInfo.chainID == getMainchainID()`. +* For each entry `chainInfo` in `chainInfos`, either `chainInfo.channelData.messageFeeTokenID == Token.getTokenIDLSK()` (corresponding to the LSK token), or `Token.getChainID(chainInfo.channelData.messageFeeTokenID)` must be equal to `chainInfo.chainID` or `interoperabilityAsset.ownChainInfo.chainID` (the message fee token must be a native token of one of the chains). + +##### Genesis State Processing + +* For every `chainInfo` in `chainInfos` with empty `chainInfo.terminatedState` add an entry to the outbox root substore with key `chainInfo.chainID` and value `chainInfo.channelData.outbox.root`. todo: Can `chainInfo` contain no outbox root after verification? +* Initialize own chain substore with data from `interoperabilityAsset.ownChainInfo`. +* For every entry `chainInfo` in `chainInfos` add the following substore entries with the key `chainInfo.chainID`: + * with the value `chainInfo.chainData` to the chain data substore; + * with the value `chainInfo.channelData` to the channel data substore; + * with the value `chainInfo.chainValidators` to the chain validators substore; + * with the value `chainInfo.terminatedState` to the terminated state substore, if `chainInfo.terminatedState` is not empty; + * with the value `chainInfo.terminatedOutbox` to the terminated outbox substore, if `chainInfo.terminatedOutbox` is not empty. +* On the mainchain, for every `chainInfo` in `chainInfos` add an entry to the registered names substore with key `chainInfo.chainData.name` and value `chainInfo.chainID`. + #### Genesis State Finalization The Interoperability module does not execute any logic during the genesis state finalization. From c84281606f328b451dbc715950aa0794965d987e Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Tue, 14 Feb 2023 11:29:58 +0100 Subject: [PATCH 02/12] Fix typo; rename ownChainInfo to ownChainAccount; add lexicographical order of chainInfos entries --- proposals/lip-0045.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/proposals/lip-0045.md b/proposals/lip-0045.md index b76ce145c..0558db37a 100644 --- a/proposals/lip-0045.md +++ b/proposals/lip-0045.md @@ -236,7 +236,7 @@ In this section, we specify the substores that are part of the Interoperability | `MIN_CHAIN_NAME_LENGTH` | uint32 | 1 | The minimum length of a string specifying the name of a chain. | | `MAX_CHAIN_NAME_LENGTH` | uint32 | 32 | The maximum length of a string specifying the name of a chain. | -We further use the utility function `getMainchainID()` defined in [LIP 0037][lip-0037#getMainchainID] to obtain the chain ID of the mainchain. +We further use the utility function `getMainchainID` defined in [LIP 0037][lip-0037#getMainchainID] to obtain the chain ID of the mainchain. #### Empty Cross-chain Message @@ -1390,11 +1390,11 @@ def isChainIDAvailable(chainID: ChainID) -> bool: genesisInteroperabilityStoreSchema = { "type": "object", "required": [ - "ownChainInfo", + "ownChainAccount", "chainInfos" ], "properties": { - "ownChainInfo": { + "ownChainAccount": { ...ownChainAccountSchema, "fieldNumber": 1 }, @@ -1454,7 +1454,7 @@ Let `genesisBlockAssetBytes` be the `data` bytes included in the genesis block a ##### Genesis Asset Verification -* Within `chainInfos`, check that all entries have a different `chainID`. +* Within `chainInfos`, check that all entries have a different `chainID` and are ordered lexicographically by `chainID`. * For every `chainInfo` in `chainInfos`, properties `chainInfo.chainData`, `chainInfo.channelData` and `chainInfo.chainValidators` must be non-empty. * For every `chainInfo` in `chainInfos` with non-empty `chainInfo.terminatedOutbox`, the field `chainInfo.terminatedState` must also be non-empty. * For every `chainInfo` in `chainInfos` with `chainInfo.terminatedState.initialized == False`, the field `chainInfo.terminatedOutbox` must be empty, `chainInfo.terminatedState.stateRoot` must be equal to `EMPTY_HASH` and `chainInfo.terminatedState.mainchainStateRoot` must not be equal to `EMPTY_HASH`. Conversely, if `chainInfo.terminatedState.initialized == True`, then `chainInfo.terminatedState.stateRoot` must not be equal to `EMPTY_HASH` and `chainInfo.terminatedState.mainchainStateRoot` must be equal to `EMPTY_HASH`. todo: must the terminated outbox be non-empty in this case? @@ -1469,16 +1469,16 @@ Let `genesisBlockAssetBytes` be the `data` bytes included in the genesis block a * Let `totalWeight` be the sum of the `bftWeight` property of every element in `activeValidators`. Then `totalWeight` has to be less than or equal to `MAX_UINT64`. * Check that `totalWeight//3 + 1 <= certificateThreshold <= totalWeight`, where `//` indicates integer division. * Check that the corresponding `validatorsHash` stored in `chainInfo.chainData.lastCertificate.validatorsHash` matches with the value computed from `activeValidators` and `certificateThreshold`. -* On a sidechain, if `chainInfos` has an entry for another sidechain (`chainID != getMainchainID()`), then there must also exist an entry for the mainchain (`chainID == getMainchainID`) and `interoperabilityAsset.ownChainInfo` must be non-empty. +* On a sidechain, if `chainInfos` has an entry for another sidechain (`chainID != getMainchainID()`), then there must also exist an entry for the mainchain (`chainID == getMainchainID()`) and `interoperabilityAsset.ownChainAccount` must be non-empty. * For each entry `chainInfo` in `chainInfos`, check that `chainInfo.chainData.lastCertificate.timestamp < g.header.timestamp`. -* For each entry `chainInfo` in `chainInfos`, check that `chainInfo.chainId[0] == interoperabilityAsset.ownChainInfo.chainID[0]` and `chainInfo.chainId != interoperabilityAsset.ownChainInfo.chainID`. -* On the mainchain, check that `interoperabilityAsset.ownChainInfo.name == CHAIN_NAME_MAINCHAIN` and `interoperabilityAsset.ownChainInfo.chainID == getMainchainID()`. -* For each entry `chainInfo` in `chainInfos`, either `chainInfo.channelData.messageFeeTokenID == Token.getTokenIDLSK()` (corresponding to the LSK token), or `Token.getChainID(chainInfo.channelData.messageFeeTokenID)` must be equal to `chainInfo.chainID` or `interoperabilityAsset.ownChainInfo.chainID` (the message fee token must be a native token of one of the chains). +* For each entry `chainInfo` in `chainInfos`, check that `chainInfo.chainId[0] == interoperabilityAsset.ownChainAccount.chainID[0]` and `chainInfo.chainId != interoperabilityAsset.ownChainAccount.chainID`. +* On the mainchain, check that `interoperabilityAsset.ownChainAccount.name == CHAIN_NAME_MAINCHAIN` and `interoperabilityAsset.ownChainAccount.chainID == getMainchainID()`. +* For each entry `chainInfo` in `chainInfos`, either `chainInfo.channelData.messageFeeTokenID == Token.getTokenIDLSK()` (corresponding to the LSK token), or `Token.getChainID(chainInfo.channelData.messageFeeTokenID)` must be equal to `chainInfo.chainID` or `interoperabilityAsset.ownChainAccount.chainID` (the message fee token must be a native token of one of the chains). ##### Genesis State Processing * For every `chainInfo` in `chainInfos` with empty `chainInfo.terminatedState` add an entry to the outbox root substore with key `chainInfo.chainID` and value `chainInfo.channelData.outbox.root`. todo: Can `chainInfo` contain no outbox root after verification? -* Initialize own chain substore with data from `interoperabilityAsset.ownChainInfo`. +* Initialize own chain substore with data from `interoperabilityAsset.ownChainAccount`. * For every entry `chainInfo` in `chainInfos` add the following substore entries with the key `chainInfo.chainID`: * with the value `chainInfo.chainData` to the chain data substore; * with the value `chainInfo.channelData` to the channel data substore; From 74d523d67ab406b6fbf6c1341698ac7eb6eb66d7 Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Tue, 14 Feb 2023 11:30:54 +0100 Subject: [PATCH 03/12] remove todo comments --- proposals/lip-0045.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/lip-0045.md b/proposals/lip-0045.md index 0558db37a..f9e80ab70 100644 --- a/proposals/lip-0045.md +++ b/proposals/lip-0045.md @@ -1457,7 +1457,7 @@ Let `genesisBlockAssetBytes` be the `data` bytes included in the genesis block a * Within `chainInfos`, check that all entries have a different `chainID` and are ordered lexicographically by `chainID`. * For every `chainInfo` in `chainInfos`, properties `chainInfo.chainData`, `chainInfo.channelData` and `chainInfo.chainValidators` must be non-empty. * For every `chainInfo` in `chainInfos` with non-empty `chainInfo.terminatedOutbox`, the field `chainInfo.terminatedState` must also be non-empty. -* For every `chainInfo` in `chainInfos` with `chainInfo.terminatedState.initialized == False`, the field `chainInfo.terminatedOutbox` must be empty, `chainInfo.terminatedState.stateRoot` must be equal to `EMPTY_HASH` and `chainInfo.terminatedState.mainchainStateRoot` must not be equal to `EMPTY_HASH`. Conversely, if `chainInfo.terminatedState.initialized == True`, then `chainInfo.terminatedState.stateRoot` must not be equal to `EMPTY_HASH` and `chainInfo.terminatedState.mainchainStateRoot` must be equal to `EMPTY_HASH`. todo: must the terminated outbox be non-empty in this case? +* For every `chainInfo` in `chainInfos` with `chainInfo.terminatedState.initialized == False`, the field `chainInfo.terminatedOutbox` must be empty, `chainInfo.terminatedState.stateRoot` must be equal to `EMPTY_HASH` and `chainInfo.terminatedState.mainchainStateRoot` must not be equal to `EMPTY_HASH`. Conversely, if `chainInfo.terminatedState.initialized == True`, then `chainInfo.terminatedState.stateRoot` must not be equal to `EMPTY_HASH` and `chainInfo.terminatedState.mainchainStateRoot` must be equal to `EMPTY_HASH`. * For each entry `chainInfo` in `chainInfos`, let `chainData = chainInfo.chainData`, then check: * all `chainData.name` are unique. * all `chainData.name` have character set `a-z0-9!@$&_.`. @@ -1477,7 +1477,7 @@ Let `genesisBlockAssetBytes` be the `data` bytes included in the genesis block a ##### Genesis State Processing -* For every `chainInfo` in `chainInfos` with empty `chainInfo.terminatedState` add an entry to the outbox root substore with key `chainInfo.chainID` and value `chainInfo.channelData.outbox.root`. todo: Can `chainInfo` contain no outbox root after verification? +* For every `chainInfo` in `chainInfos` with empty `chainInfo.terminatedState` add an entry to the outbox root substore with key `chainInfo.chainID` and value `chainInfo.channelData.outbox.root`. * Initialize own chain substore with data from `interoperabilityAsset.ownChainAccount`. * For every entry `chainInfo` in `chainInfos` add the following substore entries with the key `chainInfo.chainID`: * with the value `chainInfo.chainData` to the chain data substore; From 9f57608605725390214cd0b298d5c93569be7974 Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Tue, 14 Feb 2023 14:00:23 +0100 Subject: [PATCH 04/12] Rewrite genesis verification --- proposals/lip-0045.md | 152 ++++++++++++++++++++++++++++++++---------- 1 file changed, 115 insertions(+), 37 deletions(-) diff --git a/proposals/lip-0045.md b/proposals/lip-0045.md index f9e80ab70..ff4f73b68 100644 --- a/proposals/lip-0045.md +++ b/proposals/lip-0045.md @@ -330,6 +330,8 @@ chainDataSchema = { "properties": { "name": { "dataType": "string", + "minLength": MIN_CHAIN_NAME_LENGTH, + "maxLength": MAX_CHAIN_NAME_LENGTH, "fieldNumber": 1 }, "lastCertificate": { @@ -546,6 +548,8 @@ ownChainAccountSchema = { "properties": { "name": { "dataType": "string", + "minLength": MIN_CHAIN_NAME_LENGTH, + "maxLength": MAX_CHAIN_NAME_LENGTH, "fieldNumber": 1 }, "chainID": { @@ -1390,26 +1394,32 @@ def isChainIDAvailable(chainID: ChainID) -> bool: genesisInteroperabilityStoreSchema = { "type": "object", "required": [ - "ownChainAccount", - "chainInfos" + "ownChainName", + "ownChainNonce", + "chainInfos", + "terminatedStateAccounts", + "terminatedOutboxAccounts" ], "properties": { - "ownChainAccount": { - ...ownChainAccountSchema, + "ownChainName": { + "dataType": "string", + "maxLength": MAX_CHAIN_NAME_LENGTH, "fieldNumber": 1 }, + "ownChainNonce": { + "dataType": "uint64", + "fieldNumber": 2 + } "chainInfos": { "type": "array", - "fieldNumber": 2, + "fieldNumber": 3, "items": { "type": "object", "required": [ "chainID", "chainData", "channelData", - "chainValidators", - "terminatedState", - "terminatedOutbox" + "chainValidators" ], "properties": { "chainID": { @@ -1428,14 +1438,50 @@ genesisInteroperabilityStoreSchema = { "chainValidators": { ...chainValidatorsSchema, "fieldNumber": 4 + } + } + } + }, + "terminatedStateAccounts": { + "type": "array", + "fieldNumber": 4, + "items": { + "type": "object", + "required": [ + "chainID", + "terminatedStateAccount" + ], + "properties": { + "chainID": { + "dataType": "bytes", + "length": CHAIN_ID_LENGTH, + "fieldNumber": 1 }, - "terminatedState": { + "terminatedStateAccount": { ...terminatedStateAccountSchema, - "fieldNumber": 5 + "fieldNumber": 2 + } + } + } + }, + "terminatedOutboxAccounts": { + "type": "array", + "fieldNumber": 5, + "items": { + "type": "object", + "required": [ + "chainID", + "terminatedOutboxAccount" + ], + "properties": { + "chainID": { + "dataType": "bytes", + "length": CHAIN_ID_LENGTH, + "fieldNumber": 1 }, - "terminatedOutbox": { + "terminatedOutboxAccount": { ...terminatedOutboxAccountSchema, - "fieldNumber": 6 + "fieldNumber": 2 } } } @@ -1450,42 +1496,74 @@ Here, the `...` notation, borrowed from [JavaScript ES6 data destructuring](http During the genesis state initialization stage of a genesis block `g`, the following steps are executed. If any step fails, the block is discarded and has no further effect. -Let `genesisBlockAssetBytes` be the `data` bytes included in the genesis block assets for the Interoperability module and let `interoperabilityAsset = decode(genesisInteroperabilityStoreSchema, genesisBlockAssetBytes)`. Let `chainInfos = interoperabilityAsset.chainInfos`. +Let `genesisBlockAssetBytes` be the `data` bytes included in the genesis block assets for the Interoperability module and let `interoperabilityAsset = decode(genesisInteroperabilityStoreSchema, genesisBlockAssetBytes)`. Let `ownChainName = interoperabilityAsset.ownChainName`, `ownChainNonce = interoperabilityAsset.ownChainNonce`, `chainInfos = interoperabilityAsset.chainInfos`, `terminatedStateAccounts = interoperabilityAsset.terminatedStateAccounts`, and `terminatedOutboxAccounts = interoperabilityAsset.terminatedOutboxAccounts`. ##### Genesis Asset Verification -* Within `chainInfos`, check that all entries have a different `chainID` and are ordered lexicographically by `chainID`. -* For every `chainInfo` in `chainInfos`, properties `chainInfo.chainData`, `chainInfo.channelData` and `chainInfo.chainValidators` must be non-empty. -* For every `chainInfo` in `chainInfos` with non-empty `chainInfo.terminatedOutbox`, the field `chainInfo.terminatedState` must also be non-empty. -* For every `chainInfo` in `chainInfos` with `chainInfo.terminatedState.initialized == False`, the field `chainInfo.terminatedOutbox` must be empty, `chainInfo.terminatedState.stateRoot` must be equal to `EMPTY_HASH` and `chainInfo.terminatedState.mainchainStateRoot` must not be equal to `EMPTY_HASH`. Conversely, if `chainInfo.terminatedState.initialized == True`, then `chainInfo.terminatedState.stateRoot` must not be equal to `EMPTY_HASH` and `chainInfo.terminatedState.mainchainStateRoot` must be equal to `EMPTY_HASH`. +The genesis asset verification follows different rules on the mainchain or on a sidechain. + +On the mainchain: + +* `ownChainName == CHAIN_NAME_MAINCHAIN`. +* If `chainInfos` is non-empty, then `ownChainNonce > 0`. Conversely, if `chainInfos` is empty, then `ownChainNonce == 0`. +* Each entry `chainInfo` in `chainInfos` has a unique `chainInfo.chainID` and is ordered lexicographically by `chainInfo.chainID`. Furthermore for each entry it holds: + * `chainInfo.chainID != getMainchainID()`; + * `chainInfo.chainId[0] == getMainchainID()[0]`. * For each entry `chainInfo` in `chainInfos`, let `chainData = chainInfo.chainData`, then check: - * all `chainData.name` are unique. - * all `chainData.name` have character set `a-z0-9!@$&_.`. - * property `chainData.status` is in set `{CHAIN_STATUS_REGISTERED, CHAIN_STATUS_ACTIVE, CHAIN_STATUS_TERMINATED}`. + * `chainData.lastCertificate.timestamp < g.header.timestamp`; + * all `chainData.name` are unique; + * all `chainData.name` have character set `a-z0-9!@$&_.` and length between `MIN_CHAIN_NAME_LENGTH` and `MAX_CHAIN_NAME_LENGTH`; + * property `chainData.status` is in set `{CHAIN_STATUS_REGISTERED, CHAIN_STATUS_ACTIVE, CHAIN_STATUS_TERMINATED}`. +* For each entry `chainInfo` in `chainInfos`, let `channelData = chainInfo.channelData`, then check: + * `channelData.messageFeeTokenID == Token.getTokenIDLSK()`. * For each entry `chainInfo` in `chainInfos`, let `activeValidators = chainInfo.chainValidators.activeValidators` and let `certificateThreshold = chainInfo.chainValidators.certificateThreshold`, then check: - * `activeValidators` must have at least 1 element and at most `MAX_NUM_VALIDATORS` elements. - * `activeValidators` must be ordered lexicographically by `blsKey` property. - * All `blsKey` properties must be pairwise distinct. - * Let `totalWeight` be the sum of the `bftWeight` property of every element in `activeValidators`. Then `totalWeight` has to be less than or equal to `MAX_UINT64`. - * Check that `totalWeight//3 + 1 <= certificateThreshold <= totalWeight`, where `//` indicates integer division. - * Check that the corresponding `validatorsHash` stored in `chainInfo.chainData.lastCertificate.validatorsHash` matches with the value computed from `activeValidators` and `certificateThreshold`. -* On a sidechain, if `chainInfos` has an entry for another sidechain (`chainID != getMainchainID()`), then there must also exist an entry for the mainchain (`chainID == getMainchainID()`) and `interoperabilityAsset.ownChainAccount` must be non-empty. -* For each entry `chainInfo` in `chainInfos`, check that `chainInfo.chainData.lastCertificate.timestamp < g.header.timestamp`. -* For each entry `chainInfo` in `chainInfos`, check that `chainInfo.chainId[0] == interoperabilityAsset.ownChainAccount.chainID[0]` and `chainInfo.chainId != interoperabilityAsset.ownChainAccount.chainID`. -* On the mainchain, check that `interoperabilityAsset.ownChainAccount.name == CHAIN_NAME_MAINCHAIN` and `interoperabilityAsset.ownChainAccount.chainID == getMainchainID()`. -* For each entry `chainInfo` in `chainInfos`, either `chainInfo.channelData.messageFeeTokenID == Token.getTokenIDLSK()` (corresponding to the LSK token), or `Token.getChainID(chainInfo.channelData.messageFeeTokenID)` must be equal to `chainInfo.chainID` or `interoperabilityAsset.ownChainAccount.chainID` (the message fee token must be a native token of one of the chains). + * `activeValidators` must have at least 1 element and at most `MAX_NUM_VALIDATORS` elements; + * `activeValidators` must be ordered lexicographically by `blsKey` property; + * all `blsKey` properties must be pairwise distinct; + * let `totalWeight` be the sum of the `bftWeight` property of every element in `activeValidators`. Then `totalWeight` has to be less than or equal to `MAX_UINT64`; + * check that `totalWeight//3 + 1 <= certificateThreshold <= totalWeight`, where `//` indicates integer division; + * check that the corresponding `validatorsHash` stored in `chainInfo.chainData.lastCertificate.validatorsHash` matches with the value computed from `activeValidators` and `certificateThreshold`. +* For each entry `chainInfo` in `chainInfos`, `chainInfo.chainData.status == CHAIN_STATUS_TERMINATED` if and only if a corresponding entry (i.e., with `chainID == chainInfo.chainID`) exists in `terminatedStateAccounts`. +* Each entry `stateAccount` in `terminatedStateAccounts` has a unique `stateAccount.chainID` and is ordered lexicographically by `stateAccount.chainID`. +* For each entry `stateAccount` in `terminatedStateAccounts` holds `stateAccount.stateRoot == chainData.lastCertificate.stateRoot`, `stateAccount.mainchainStateRoot == EMPTY_HASH`, and `stateAccount.initialized == True`. Here `chainData` is the corresponding entry (i.e., with `chainID == stateAccount.chainID`) in `chainInfos`. +* Each entry `outboxAccount` in `terminatedOutboxAccounts` has a unique `outboxAccount.chainID` and is ordered lexicographically by `outboxAccount.chainID`. Furthermore, an entry `outboxAccount` in `terminatedOutboxAccounts` must have a corresponding entry (i.e., with `chainID == outboxAccount.chainID`) in `terminatedStateAccounts`. Notice that the opposite is not necessarily true, so that there could be an entry in `terminatedStateAccounts` without a corresponding entry in `terminatedOutboxAccounts`. + + +On a sidechain, the Interoperability state can only contain the chain account for the mainchain (if the mainchain registration was done) or be empty. Hence, `chainInfos` is either empty or it contains exactly one entry for the mainchain. + +If `chainInfos` is empty, then check that: + +* `ownChainName` is the empty string; +* `ownChainNonce == 0`; +* `terminatedStateAccounts` is empty; +* `terminatedOutboxAccounts` is empty. + + +If `chainInfos` is not empty, then check that: + +* `ownChainName` has length between `MIN_CHAIN_NAME_LENGTH` and `MAX_CHAIN_NAME_LENGTH`, and `ownChainName != CHAIN_NAME_MAINCHAIN`; +* `ownChainNonce > 0`; +* `chainInfos` contains exactly one entry with: + * `chainID == getMainchainID()`; + * `chainData.name == CHAIN_NAME_MAINCHAIN`, `chainData.status != CHAIN_STATUS_TERMINATED`, and `chainData.lastCertificate.timestamp < g.header.timestamp`; + * `channelData.messageFeeTokenID == Token.getTokenIDLSK()`. +* Each entry `stateAccount` in `terminatedStateAccounts` has a unique `stateAccount.chainID` and is ordered lexicographically by `stateAccount.chainID`. Furthermore for each entry it holds `stateAccount.chainID != getMainchainID()` and `stateAccount.chainID != ownChainAccount.chainID`. +* For each entry `stateAccount` in `terminatedStateAccounts` either: + * `stateAccount.stateRoot != EMPTY_HASH`, `stateAccount.mainchainStateRoot == EMPTY_HASH`, and `stateAccount.initialized == True`; + * or `stateAccount.stateRoot == EMPTY_HASH`, `stateAccount.mainchainStateRoot != EMPTY_HASH`, and `stateAccount.initialized == False`. +* `terminatedOutboxAccounts` is empty; ##### Genesis State Processing -* For every `chainInfo` in `chainInfos` with empty `chainInfo.terminatedState` add an entry to the outbox root substore with key `chainInfo.chainID` and value `chainInfo.channelData.outbox.root`. -* Initialize own chain substore with data from `interoperabilityAsset.ownChainAccount`. -* For every entry `chainInfo` in `chainInfos` add the following substore entries with the key `chainInfo.chainID`: +* Add an entry to the own chain substore with key set to `EMPTY_BYTES` and value set to `{"name": ownChainName, "chainID": OWN_CHAIN_ID, "nonce": ownChainNonce}`. +* For each entry `chainInfo` in `chainInfos` add the following substore entries with key set to `chainInfo.chainID`: * with the value `chainInfo.chainData` to the chain data substore; * with the value `chainInfo.channelData` to the channel data substore; * with the value `chainInfo.chainValidators` to the chain validators substore; - * with the value `chainInfo.terminatedState` to the terminated state substore, if `chainInfo.terminatedState` is not empty; - * with the value `chainInfo.terminatedOutbox` to the terminated outbox substore, if `chainInfo.terminatedOutbox` is not empty. -* On the mainchain, for every `chainInfo` in `chainInfos` add an entry to the registered names substore with key `chainInfo.chainData.name` and value `chainInfo.chainID`. + * with the value `chainInfo.channelData.outbox.root` to the outbox root substore, only if `chainInfo.chainData.status != CHAIN_STATUS_TERMINATED`. +* For each entry `stateAccount` in `terminatedStateAccounts` add an entry to the terminated state substore with key set to `stateAccount.chainID` and value set to `stateAccount.terminatedStateAccount`. +* For each entry `outboxAccount` in `terminatedOutboxAccounts` add an entry to the terminated outbox substore with key set to `outboxAccount.chainID` and value set to `outboxAccount.terminatedOutboxAccount`. +* On the mainchain, for each `chainInfo` in `chainInfos` add an entry to the registered names substore with key `chainInfo.chainData.name` and value `chainInfo.chainID`. Furthermore add an entry for the mainchain with key `CHAIN_NAME_MAINCHAIN` and value `getMainchainID()`. #### Genesis State Finalization From 2355a7260f123b3e4b2fcbf77276b374f90e47a4 Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Tue, 14 Feb 2023 14:48:30 +0100 Subject: [PATCH 05/12] minor extra check --- proposals/lip-0045.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/lip-0045.md b/proposals/lip-0045.md index ff4f73b68..36da9dc61 100644 --- a/proposals/lip-0045.md +++ b/proposals/lip-0045.md @@ -1512,7 +1512,7 @@ On the mainchain: * For each entry `chainInfo` in `chainInfos`, let `chainData = chainInfo.chainData`, then check: * `chainData.lastCertificate.timestamp < g.header.timestamp`; * all `chainData.name` are unique; - * all `chainData.name` have character set `a-z0-9!@$&_.` and length between `MIN_CHAIN_NAME_LENGTH` and `MAX_CHAIN_NAME_LENGTH`; + * all `chainData.name` are from the character set `a-z0-9!@$&_.` and length between `MIN_CHAIN_NAME_LENGTH` and `MAX_CHAIN_NAME_LENGTH`; * property `chainData.status` is in set `{CHAIN_STATUS_REGISTERED, CHAIN_STATUS_ACTIVE, CHAIN_STATUS_TERMINATED}`. * For each entry `chainInfo` in `chainInfos`, let `channelData = chainInfo.channelData`, then check: * `channelData.messageFeeTokenID == Token.getTokenIDLSK()`. @@ -1541,7 +1541,7 @@ If `chainInfos` is empty, then check that: If `chainInfos` is not empty, then check that: -* `ownChainName` has length between `MIN_CHAIN_NAME_LENGTH` and `MAX_CHAIN_NAME_LENGTH`, and `ownChainName != CHAIN_NAME_MAINCHAIN`; +* `ownChainName` is from the character set `a-z0-9!@$&_.`, has length between `MIN_CHAIN_NAME_LENGTH` and `MAX_CHAIN_NAME_LENGTH`, and `ownChainName != CHAIN_NAME_MAINCHAIN`; * `ownChainNonce > 0`; * `chainInfos` contains exactly one entry with: * `chainID == getMainchainID()`; From 82dbffd4897e74f4d454acce0ccabf21ebddcb26 Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Tue, 14 Feb 2023 14:51:18 +0100 Subject: [PATCH 06/12] fix initialization own chain account --- proposals/lip-0045.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/lip-0045.md b/proposals/lip-0045.md index 36da9dc61..2644692f6 100644 --- a/proposals/lip-0045.md +++ b/proposals/lip-0045.md @@ -1555,7 +1555,7 @@ If `chainInfos` is not empty, then check that: ##### Genesis State Processing -* Add an entry to the own chain substore with key set to `EMPTY_BYTES` and value set to `{"name": ownChainName, "chainID": OWN_CHAIN_ID, "nonce": ownChainNonce}`. +* If `ownChainName` is not the empty string and `ownChainNonce != 0`, add an entry to the own chain substore with key set to `EMPTY_BYTES` and value set to `{"name": ownChainName, "chainID": OWN_CHAIN_ID, "nonce": ownChainNonce}`. * For each entry `chainInfo` in `chainInfos` add the following substore entries with key set to `chainInfo.chainID`: * with the value `chainInfo.chainData` to the chain data substore; * with the value `chainInfo.channelData` to the channel data substore; From 982986a400a7ec0213a5fc00d4e2912855f0cb0a Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Tue, 14 Feb 2023 16:15:11 +0100 Subject: [PATCH 07/12] Add check for escrow account --- proposals/lip-0045.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/lip-0045.md b/proposals/lip-0045.md index 2644692f6..30fd5e17e 100644 --- a/proposals/lip-0045.md +++ b/proposals/lip-0045.md @@ -1567,7 +1567,7 @@ If `chainInfos` is not empty, then check that: #### Genesis State Finalization -The Interoperability module does not execute any logic during the genesis state finalization. +* For each entry `chainInfo` in `chainInfos`, check that `Token.escrowSubstoreExists(chainInfo.chainID, chainInfo.channelData.messageFeeTokenID) == True`. ## Backwards Compatibility From 752c0b310db4c7a347338289bba2ec306953ea29 Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Thu, 16 Feb 2023 15:17:40 +0100 Subject: [PATCH 08/12] Apply suggestions from code review Co-authored-by: AndreasKendziorra <40799768+AndreasKendziorra@users.noreply.github.com> --- proposals/lip-0045.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/proposals/lip-0045.md b/proposals/lip-0045.md index 30fd5e17e..337948736 100644 --- a/proposals/lip-0045.md +++ b/proposals/lip-0045.md @@ -1506,7 +1506,7 @@ On the mainchain: * `ownChainName == CHAIN_NAME_MAINCHAIN`. * If `chainInfos` is non-empty, then `ownChainNonce > 0`. Conversely, if `chainInfos` is empty, then `ownChainNonce == 0`. -* Each entry `chainInfo` in `chainInfos` has a unique `chainInfo.chainID` and is ordered lexicographically by `chainInfo.chainID`. Furthermore for each entry it holds: +* Each entry `chainInfo` in `chainInfos` has a unique `chainInfo.chainID` and `chainInfos` is ordered lexicographically by `chainInfo.chainID`. Furthermore for each entry it holds: * `chainInfo.chainID != getMainchainID()`; * `chainInfo.chainId[0] == getMainchainID()[0]`. * For each entry `chainInfo` in `chainInfos`, let `chainData = chainInfo.chainData`, then check: @@ -1524,9 +1524,9 @@ On the mainchain: * check that `totalWeight//3 + 1 <= certificateThreshold <= totalWeight`, where `//` indicates integer division; * check that the corresponding `validatorsHash` stored in `chainInfo.chainData.lastCertificate.validatorsHash` matches with the value computed from `activeValidators` and `certificateThreshold`. * For each entry `chainInfo` in `chainInfos`, `chainInfo.chainData.status == CHAIN_STATUS_TERMINATED` if and only if a corresponding entry (i.e., with `chainID == chainInfo.chainID`) exists in `terminatedStateAccounts`. -* Each entry `stateAccount` in `terminatedStateAccounts` has a unique `stateAccount.chainID` and is ordered lexicographically by `stateAccount.chainID`. +* Each entry `stateAccount` in `terminatedStateAccounts` has a unique `stateAccount.chainID` and `terminatedStateAccounts` is ordered lexicographically by `stateAccount.chainID`. * For each entry `stateAccount` in `terminatedStateAccounts` holds `stateAccount.stateRoot == chainData.lastCertificate.stateRoot`, `stateAccount.mainchainStateRoot == EMPTY_HASH`, and `stateAccount.initialized == True`. Here `chainData` is the corresponding entry (i.e., with `chainID == stateAccount.chainID`) in `chainInfos`. -* Each entry `outboxAccount` in `terminatedOutboxAccounts` has a unique `outboxAccount.chainID` and is ordered lexicographically by `outboxAccount.chainID`. Furthermore, an entry `outboxAccount` in `terminatedOutboxAccounts` must have a corresponding entry (i.e., with `chainID == outboxAccount.chainID`) in `terminatedStateAccounts`. Notice that the opposite is not necessarily true, so that there could be an entry in `terminatedStateAccounts` without a corresponding entry in `terminatedOutboxAccounts`. +* Each entry `outboxAccount` in `terminatedOutboxAccounts` has a unique `outboxAccount.chainID` and `terminatedOutboxAccounts` is ordered lexicographically by `outboxAccount.chainID`. Furthermore, an entry `outboxAccount` in `terminatedOutboxAccounts` must have a corresponding entry (i.e., with `chainID == outboxAccount.chainID`) in `terminatedStateAccounts`. Notice that the opposite is not necessarily true, so that there could be an entry in `terminatedStateAccounts` without a corresponding entry in `terminatedOutboxAccounts`. On a sidechain, the Interoperability state can only contain the chain account for the mainchain (if the mainchain registration was done) or be empty. Hence, `chainInfos` is either empty or it contains exactly one entry for the mainchain. @@ -1545,9 +1545,9 @@ If `chainInfos` is not empty, then check that: * `ownChainNonce > 0`; * `chainInfos` contains exactly one entry with: * `chainID == getMainchainID()`; - * `chainData.name == CHAIN_NAME_MAINCHAIN`, `chainData.status != CHAIN_STATUS_TERMINATED`, and `chainData.lastCertificate.timestamp < g.header.timestamp`; + * `chainData.name == CHAIN_NAME_MAINCHAIN`, `chainData.status != CHAIN_STATUS_TERMINATED`, and `chainData.lastCertificate.timestamp < g.header.timestamp`; * `channelData.messageFeeTokenID == Token.getTokenIDLSK()`. -* Each entry `stateAccount` in `terminatedStateAccounts` has a unique `stateAccount.chainID` and is ordered lexicographically by `stateAccount.chainID`. Furthermore for each entry it holds `stateAccount.chainID != getMainchainID()` and `stateAccount.chainID != ownChainAccount.chainID`. +* Each entry `stateAccount` in `terminatedStateAccounts` has a unique `stateAccount.chainID` and `terminatedStateAccounts` is ordered lexicographically by `stateAccount.chainID`. Furthermore for each entry it holds `stateAccount.chainID != getMainchainID()` and `stateAccount.chainID != ownChainAccount.chainID`. * For each entry `stateAccount` in `terminatedStateAccounts` either: * `stateAccount.stateRoot != EMPTY_HASH`, `stateAccount.mainchainStateRoot == EMPTY_HASH`, and `stateAccount.initialized == True`; * or `stateAccount.stateRoot == EMPTY_HASH`, `stateAccount.mainchainStateRoot != EMPTY_HASH`, and `stateAccount.initialized == False`. From 38dacff9eca4f7cb845eedb03dd3b7343d8e8131 Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Fri, 17 Feb 2023 08:49:49 +0100 Subject: [PATCH 09/12] Address Anderas' review --- proposals/lip-0045.md | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/proposals/lip-0045.md b/proposals/lip-0045.md index 337948736..1951aaea8 100644 --- a/proposals/lip-0045.md +++ b/proposals/lip-0045.md @@ -1500,26 +1500,29 @@ Let `genesisBlockAssetBytes` be the `data` bytes included in the genesis block a ##### Genesis Asset Verification -The genesis asset verification follows different rules on the mainchain or on a sidechain. +The genesis asset verification follows different rules on the mainchain or on a sidechain. In both cases, it is checked that `validateObjectSchema(genesisInteroperabilityStoreSchema, genesisBlockAssetBytes)` does not throw an error. -On the mainchain: +###### Mainchain + +On the mainchain, the following checks are performed: * `ownChainName == CHAIN_NAME_MAINCHAIN`. * If `chainInfos` is non-empty, then `ownChainNonce > 0`. Conversely, if `chainInfos` is empty, then `ownChainNonce == 0`. * Each entry `chainInfo` in `chainInfos` has a unique `chainInfo.chainID` and `chainInfos` is ordered lexicographically by `chainInfo.chainID`. Furthermore for each entry it holds: * `chainInfo.chainID != getMainchainID()`; * `chainInfo.chainId[0] == getMainchainID()[0]`. -* For each entry `chainInfo` in `chainInfos`, let `chainData = chainInfo.chainData`, then check: +* For each entry `chainInfo` in `chainInfos`, let `chainData = chainInfo.chainData`. The entries `chainData.name` must be pairwise distinct. Furthermore for each entry it holds: * `chainData.lastCertificate.timestamp < g.header.timestamp`; - * all `chainData.name` are unique; - * all `chainData.name` are from the character set `a-z0-9!@$&_.` and length between `MIN_CHAIN_NAME_LENGTH` and `MAX_CHAIN_NAME_LENGTH`; + * `chainData.name` only uses the character set `a-z0-9!@$&_.`; * property `chainData.status` is in set `{CHAIN_STATUS_REGISTERED, CHAIN_STATUS_ACTIVE, CHAIN_STATUS_TERMINATED}`. * For each entry `chainInfo` in `chainInfos`, let `channelData = chainInfo.channelData`, then check: - * `channelData.messageFeeTokenID == Token.getTokenIDLSK()`. + * `channelData.messageFeeTokenID == Token.getTokenIDLSK()`; + * `channelData.minReturnFeePerByte == MIN_RETURN_FEE_PER_BYTE_LSK`. * For each entry `chainInfo` in `chainInfos`, let `activeValidators = chainInfo.chainValidators.activeValidators` and let `certificateThreshold = chainInfo.chainValidators.certificateThreshold`, then check: * `activeValidators` must have at least 1 element and at most `MAX_NUM_VALIDATORS` elements; * `activeValidators` must be ordered lexicographically by `blsKey` property; - * all `blsKey` properties must be pairwise distinct; + * all `blsKey` properties must be pairwise distinct; + * for each `validator` in `activeValidators`, check that `validator.bftWeight > 0`; * let `totalWeight` be the sum of the `bftWeight` property of every element in `activeValidators`. Then `totalWeight` has to be less than or equal to `MAX_UINT64`; * check that `totalWeight//3 + 1 <= certificateThreshold <= totalWeight`, where `//` indicates integer division; * check that the corresponding `validatorsHash` stored in `chainInfo.chainData.lastCertificate.validatorsHash` matches with the value computed from `activeValidators` and `certificateThreshold`. @@ -1529,6 +1532,8 @@ On the mainchain: * Each entry `outboxAccount` in `terminatedOutboxAccounts` has a unique `outboxAccount.chainID` and `terminatedOutboxAccounts` is ordered lexicographically by `outboxAccount.chainID`. Furthermore, an entry `outboxAccount` in `terminatedOutboxAccounts` must have a corresponding entry (i.e., with `chainID == outboxAccount.chainID`) in `terminatedStateAccounts`. Notice that the opposite is not necessarily true, so that there could be an entry in `terminatedStateAccounts` without a corresponding entry in `terminatedOutboxAccounts`. +###### Sidechain + On a sidechain, the Interoperability state can only contain the chain account for the mainchain (if the mainchain registration was done) or be empty. Hence, `chainInfos` is either empty or it contains exactly one entry for the mainchain. If `chainInfos` is empty, then check that: @@ -1543,10 +1548,19 @@ If `chainInfos` is not empty, then check that: * `ownChainName` is from the character set `a-z0-9!@$&_.`, has length between `MIN_CHAIN_NAME_LENGTH` and `MAX_CHAIN_NAME_LENGTH`, and `ownChainName != CHAIN_NAME_MAINCHAIN`; * `ownChainNonce > 0`; -* `chainInfos` contains exactly one entry with: - * `chainID == getMainchainID()`; - * `chainData.name == CHAIN_NAME_MAINCHAIN`, `chainData.status != CHAIN_STATUS_TERMINATED`, and `chainData.lastCertificate.timestamp < g.header.timestamp`; - * `channelData.messageFeeTokenID == Token.getTokenIDLSK()`. +* `chainInfos` contains exactly one entry `mainchainInfo` with: + * `mainchainInfo.chainID == getMainchainID()`; + * `mainchainInfo.chainData.name == CHAIN_NAME_MAINCHAIN`, `chainData.status != CHAIN_STATUS_TERMINATED`, and `chainData.lastCertificate.timestamp < g.header.timestamp`; + * `mainchainInfo.channelData.messageFeeTokenID == Token.getTokenIDLSK()`; + * `mainchainInfo.channelData.minReturnFeePerByte == MIN_RETURN_FEE_PER_BYTE_LSK`. +* Let `activeValidators = mainchainInfo.chainValidators.activeValidators` and let `certificateThreshold = mainchainInfo.chainValidators.certificateThreshold`, then check: + * `activeValidators` must have at least 1 element and at most `MAX_NUM_VALIDATORS` elements; + * `activeValidators` must be ordered lexicographically by `blsKey` property; + * all `blsKey` properties must be pairwise distinct; + * for each `validator` in `activeValidators`, check that `validator.bftWeight > 0`; + * let `totalWeight` be the sum of the `bftWeight` property of every element in `activeValidators`. Then `totalWeight` has to be less than or equal to `MAX_UINT64`; + * check that `totalWeight//3 + 1 <= certificateThreshold <= totalWeight`, where `//` indicates integer division; + * check that the corresponding `validatorsHash` stored in `mainchainInfo.chainData.lastCertificate.validatorsHash` matches with the value computed from `activeValidators` and `certificateThreshold`. * Each entry `stateAccount` in `terminatedStateAccounts` has a unique `stateAccount.chainID` and `terminatedStateAccounts` is ordered lexicographically by `stateAccount.chainID`. Furthermore for each entry it holds `stateAccount.chainID != getMainchainID()` and `stateAccount.chainID != ownChainAccount.chainID`. * For each entry `stateAccount` in `terminatedStateAccounts` either: * `stateAccount.stateRoot != EMPTY_HASH`, `stateAccount.mainchainStateRoot == EMPTY_HASH`, and `stateAccount.initialized == True`; From 75c36c62c2980a2aae2c7f17a45da6007f9cf5e4 Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Fri, 17 Feb 2023 11:36:40 +0100 Subject: [PATCH 10/12] Apply suggestions from code review Co-authored-by: AndreasKendziorra <40799768+AndreasKendziorra@users.noreply.github.com> --- proposals/lip-0045.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/lip-0045.md b/proposals/lip-0045.md index 1951aaea8..e1db6dcc7 100644 --- a/proposals/lip-0045.md +++ b/proposals/lip-0045.md @@ -1522,7 +1522,7 @@ On the mainchain, the following checks are performed: * `activeValidators` must have at least 1 element and at most `MAX_NUM_VALIDATORS` elements; * `activeValidators` must be ordered lexicographically by `blsKey` property; * all `blsKey` properties must be pairwise distinct; - * for each `validator` in `activeValidators`, check that `validator.bftWeight > 0`; + * for each `validator` in `activeValidators`, `validator.bftWeight > 0` must hold; * let `totalWeight` be the sum of the `bftWeight` property of every element in `activeValidators`. Then `totalWeight` has to be less than or equal to `MAX_UINT64`; * check that `totalWeight//3 + 1 <= certificateThreshold <= totalWeight`, where `//` indicates integer division; * check that the corresponding `validatorsHash` stored in `chainInfo.chainData.lastCertificate.validatorsHash` matches with the value computed from `activeValidators` and `certificateThreshold`. @@ -1550,14 +1550,14 @@ If `chainInfos` is not empty, then check that: * `ownChainNonce > 0`; * `chainInfos` contains exactly one entry `mainchainInfo` with: * `mainchainInfo.chainID == getMainchainID()`; - * `mainchainInfo.chainData.name == CHAIN_NAME_MAINCHAIN`, `chainData.status != CHAIN_STATUS_TERMINATED`, and `chainData.lastCertificate.timestamp < g.header.timestamp`; + * `mainchainInfo.chainData.name == CHAIN_NAME_MAINCHAIN`, `mainchainInfo.chainData.status` is either equal to `CHAIN_STATUS_REGISTERED` or to `CHAIN_STATUS_ACTIVE`, and `mainchainInfo.chainData.lastCertificate.timestamp < g.header.timestamp`; * `mainchainInfo.channelData.messageFeeTokenID == Token.getTokenIDLSK()`; * `mainchainInfo.channelData.minReturnFeePerByte == MIN_RETURN_FEE_PER_BYTE_LSK`. * Let `activeValidators = mainchainInfo.chainValidators.activeValidators` and let `certificateThreshold = mainchainInfo.chainValidators.certificateThreshold`, then check: * `activeValidators` must have at least 1 element and at most `MAX_NUM_VALIDATORS` elements; * `activeValidators` must be ordered lexicographically by `blsKey` property; * all `blsKey` properties must be pairwise distinct; - * for each `validator` in `activeValidators`, check that `validator.bftWeight > 0`; + * for each `validator` in `activeValidators`, `validator.bftWeight > 0` must hold; * let `totalWeight` be the sum of the `bftWeight` property of every element in `activeValidators`. Then `totalWeight` has to be less than or equal to `MAX_UINT64`; * check that `totalWeight//3 + 1 <= certificateThreshold <= totalWeight`, where `//` indicates integer division; * check that the corresponding `validatorsHash` stored in `mainchainInfo.chainData.lastCertificate.validatorsHash` matches with the value computed from `activeValidators` and `certificateThreshold`. From ddf301bfaf83cf10c8bf788ee460e378fbd9008e Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Fri, 17 Feb 2023 18:02:57 +0100 Subject: [PATCH 11/12] address Greg's review --- proposals/lip-0045.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/lip-0045.md b/proposals/lip-0045.md index e1db6dcc7..18b6bf2e0 100644 --- a/proposals/lip-0045.md +++ b/proposals/lip-0045.md @@ -212,7 +212,7 @@ In this section, we specify the substores that are part of the Interoperability | `OWN_CHAIN_ID` | bytes | | The chain ID of the chain under consideration. | | `CHAIN_REGISTRATION_FEE` | uint64 | 1000000000 | Fee to pay for a sidechain registration command in Beddows. | | `EMPTY_HASH` | bytes | `sha256(b"")` | SHA-256 hash of empty bytes. | -| `EMPTY_CCM` | CCM | An object following the [`crossChainMessageSchema`][lip-0049#ccmschema] schema with all properties set to their default values (see exact definition [below](#empty-cross-chain-message)). | The empty ccm object. | +| `EMPTY_CCM` | CCM | A CCM object partially following the [`crossChainMessageSchema`][lip-0049#ccmschema] schema with all properties set to their default values (see exact definition [below](#empty-cross-chain-message)). | The empty ccm object. | | `LIVENESS_LIMIT` | uint32 | 30x24x3600 | The maximum time interval for the liveness condition. | | `MAX_NUM_VALIDATORS` | uint32 | 199 | The maximum number of validators that can be registered.| | `MAX_UINT64` | uint64 | 18446744073709551615 | The maximum value that can be encoded in a uint64. | From 3161ad66a2a64e2b9ed66cd3ce3fe3738cc7c033 Mon Sep 17 00:00:00 2001 From: janhack <29209167+janhack@users.noreply.github.com> Date: Fri, 17 Feb 2023 18:40:08 +0100 Subject: [PATCH 12/12] Editorial edits --- proposals/lip-0045.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/lip-0045.md b/proposals/lip-0045.md index 18b6bf2e0..5d6349141 100644 --- a/proposals/lip-0045.md +++ b/proposals/lip-0045.md @@ -7,7 +7,7 @@ Discussions-To: https://research.lisk.com/t/introduce-interoperability-module/29 Status: Draft Type: Standards Track Created: 2021-05-21 -Updated: 2023-01-30 +Updated: 2023-02-17 Requires: 0043, 0049, 0053, 0054 ```