From e00b475a997963a7f2c4cb3d518130fd02bfbd42 Mon Sep 17 00:00:00 2001 From: alindima Date: Mon, 13 Nov 2023 10:13:34 +0200 Subject: [PATCH 01/11] Random assignment of availability-chunk indices to validators Signed-off-by: alindima --- ...andom-assignment-of-availability-chunks.md | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 text/0047-random-assignment-of-availability-chunks.md diff --git a/text/0047-random-assignment-of-availability-chunks.md b/text/0047-random-assignment-of-availability-chunks.md new file mode 100644 index 000000000..aa31563a6 --- /dev/null +++ b/text/0047-random-assignment-of-availability-chunks.md @@ -0,0 +1,179 @@ +# RFC-0047: Random assignment of availability chunks to validators + +| | | +| --------------- | ------------------------------------------------------------------------------------------- | +| **Start Date** | 03 November 2023 | +| **Description** | An evenly-distributing indirection layer between availability chunks and the validators that hold them.| +| **Authors** | Alin Dima | + +## Summary + +Propose a way of randomly permuting the availability chunk indices assigned to validators for a given core and relay chain block, +in the context of [recovering available data from systematic chunks](https://github.com/paritytech/polkadot-sdk/issues/598). + +## Motivation + +The relay chain node must have a deterministic way of evenly distributing the first ~(N_VALIDATORS / 3) systematic availability chunks to different validators, +based on the session, relay chain block number and core. +This is needed in order to optimise network load distribution as evenly as possible during availability recovery. + +## Stakeholders + +Relay chain node core developers. + +## Explanation + +### Systematic erasure codes + +An erasure coding algorithm is considered systematic if it preserves the original unencoded data as part of the resulting code. +[The implementation of the erasure coding algorithm used for polkadot's availability data](https://github.com/paritytech/reed-solomon-novelpoly) is systematic. Roughly speaking, the first N_VALIDATORS/3 +chunks of data can be cheaply concatenated to retrieve the original data, without running the resource-intensive and time-consuming decoding algorithm. + +### Availability recovery from systematic chunks + +As part of the effort of [increasing polkadot's resource efficiency, scalability and performance](https://github.com/paritytech/roadmap/issues/26), work is under way to +modify the Availability Recovery subsystem by leveraging systematic chunks. See [this comment](https://github.com/paritytech/polkadot-sdk/issues/598#issuecomment-1792007099) +for preliminary performance results. + +In this scheme, the relay chain node will first attempt to retrieve the N/3 systematic chunks from the validators that should hold them, +before falling back to recovering from regular chunks, as before. + +The problem that this RFC aims to address is that, currently, the ValidatorIndex is always identical to the ChunkIndex. +Since the validator array is only shuffled once per session, naively using the ValidatorIndex as the ChunkIndex +would pose an unreasonable stress on the first N/3 validators during an entire session. + +### Chunk assignment function + +#### Properties +The function that decides the chunk index for a validator should be parametrised by at least `(validator_index, session_index, block_number, core_index)` +and have the following properties: +1. deterministic +1. pseudo-random +1. relatively quick to compute and resource-efficient. +1. when considering the other params besides `validator_index` as fixed, +the function should describe a random permutation of the chunk indices +1. considering `session_index` and `block_number` as fixed arguments, the validators that map to the first N/3 chunk indices should +have as little overlap as possible for different cores. + +#### Proposed function and runtime API + +Pseudocode: + +```rust +pub fn get_chunk_index( + n_validators: u32, + validator_index: ValidatorIndex, + session_index: SessionIndex, + block_number: BlockNumber, + core_index: CoreIndex +) -> ChunkIndex { + let threshold = systematic_threshold(n_validators); // Roughly n_validators/3 + let seed = derive_seed(session_index, block_number); + let mut rng: ChaCha8Rng = SeedableRng::from_seed(seed); + let mut chunk_indices: Vec = (0..n_validators).map(Into::into).collect(); + chunk_indices.shuffle(&mut rng); + + let core_start_pos = threshold * core_index.0; + return chunk_indices[(core_start_pos + validator_index) % n_validators] +} +``` + +The function should be implemented as a runtime API, because: + +1. it's critical to the consensus protocol that all validators have a common view of the Validator->Chunk mapping. +1. it enables further atomic changes to the shuffling algorithm. +1. it enables alternative client implementations (in other languages) to use it +1. mitigates the problem of third-party libraries changing the implementations of the `ChaCha8Rng` or the `rand::shuffle` +that could be introduced in further versions, which would stall parachains. This would be quite an "easy" attack. + +Additionally, so that client code is able to efficiently get the mapping from the runtime, another API will be added +for retrieving chunk indices in bulk for all validators at a given block and core. + +#### Upgrade path + +Considering that the Validator->Chunk mapping is critical to para consensus, the change needs to be enacted atomically via +governance, only after all validators have upgraded the node to a version that is aware of this mapping. +It needs to be explicitly stated that after the runtime upgrade and governance enactment, validators that run older client versions that don't +support this mapping will not be able to participate in parachain consensus. + +### Getting access to core_index in different subsystems + +Availability-recovery can currently be triggered by three actors: +1. `ApprovalVoting` subsystem of the relay chain validator. +2. `pov_recovery` task of collators +3. `DisputeCoordinator` subsystem of the relay chain validator. + +The one parameter of the assignment function that poses problems to some subsystems is the `core_index`. +The `core_index` refers to the index of the core that the candidate was occupying while it was pending availability (from backing to inclusion). + +1. The `ApprovalVoting` subsystem starts the approval process on a candidate as a result to seeing a `CandidateIncluded` event. +This event also contains the core index that the candidate is leaving, so getting access to the core index is not an issue. +1. The `pov_recovery` task of the collators starts availability recovery in response to a `CandidateBacked` event, which enables +easy access to the core index the candidate started occupying. +1. Disputes may be initiated on a number of occasions: + + 3.a. is initiated by the current node as a result of finding an invalid candidate while doing approval work. In this case, + availability-recovery is not needed, since the validator already issued their vote. + + 3.b is initiated by noticing dispute votes recorded as a result of chain scraping. In this case, the subsystem + assumes it already has a copy of the `CandidateReceipt` as a result of scraping `CandidateBacked` events. The logic can be modified + to also record core indices. + + 3.c is initiated as a result of getting a dispute statement from another validator. It is possible that the dispute is happening on + a fork that was not yet imported by this validator, so the subsystem will not always have access to the `CandidateBacked` event + recorded by the chain scraper. + +As a solution to 3.c, a new version for the disputes request-response networking protocol will be added. +This message type will include the relay block hash where the candidate was included. This information will be used +in order to query the runtime API and retrieve the core index that the candidate was occupying. + +The usage of the systematic data availability recovery feature will also be subject to all nodes using the V2 disputes networking protocol. + +#### Alternatives to using core_index + +As an alternative to core_index, the `ParaId` could be used. It has the advantage of being readily available in the +`CandidateReceipt`, which would enable the dispute communication protocol to not change and would simplify the implementation. +However, in the context of [CoreJam](https://github.com/polkadot-fellows/RFCs/pull/31), `ParaId`s will no longer exist (at least not in their current form). + +## Drawbacks + +Has a fair amount of technical complexity involved: + +- Introduces another runtime API that is going to be issued by multiple subsystems. With adequate client-side caching, this should be acceptable. + +- Requires a networking protocol upgrade on the disputes request-response protocol + +## Testing, Security, and Privacy + +Extensive testing will be conducted - both automated and manual. +This proposal doesn't affect security or privacy. + +## Performance, Ergonomics, and Compatibility + +### Performance + +This is a necessary optimisation, as reed-solomon erasure coding has proven to be a top consumer of CPU time in polkadot when scaling the parachain count. + +With this optimisation, preliminary performance results show that CPU time used for reed-solomon coding can be halved and total POV recovery time decrease by 80% for large POVs. See more [here](https://github.com/paritytech/polkadot-sdk/issues/598#issuecomment-1792007099). + +### Ergonomics + +Not applicable. + +### Compatibility + +This is a breaking change. See "upgrade path" section above. +All validators need to have upgraded their node versions before the feature will be enabled via a runtime upgrade and governance call. + +## Prior Art and References + +See comments on the [tracking issue](https://github.com/paritytech/polkadot-sdk/issues/598) and the [in-progress PR](https://github.com/paritytech/polkadot-sdk/pull/1644) + +## Unresolved Questions + +- Is it a future-proof idea to utilise the core index as a parameter of the chunk index compute function? Is there a better alternative that avoid complicating the implementation? +- Is there a better upgrade path that would preserve backwards compatibility? + +## Future Directions and Related Material + +This enables future optimisations for the performance of availability recovery, such as retrieving batched systematic chunks from backers/approval-checkers. From 52903f3501dd2766542a5fba137d28653c86990c Mon Sep 17 00:00:00 2001 From: alindima Date: Mon, 13 Nov 2023 15:57:45 +0200 Subject: [PATCH 02/11] address some comments --- ...andom-assignment-of-availability-chunks.md | 133 +++++++++++------- 1 file changed, 83 insertions(+), 50 deletions(-) diff --git a/text/0047-random-assignment-of-availability-chunks.md b/text/0047-random-assignment-of-availability-chunks.md index aa31563a6..8fa456fc7 100644 --- a/text/0047-random-assignment-of-availability-chunks.md +++ b/text/0047-random-assignment-of-availability-chunks.md @@ -3,19 +3,25 @@ | | | | --------------- | ------------------------------------------------------------------------------------------- | | **Start Date** | 03 November 2023 | -| **Description** | An evenly-distributing indirection layer between availability chunks and the validators that hold them.| +| **Description** | An evenly-distributing indirection layer between availability chunks and validators . | | **Authors** | Alin Dima | ## Summary -Propose a way of randomly permuting the availability chunk indices assigned to validators for a given core and relay chain block, -in the context of [recovering available data from systematic chunks](https://github.com/paritytech/polkadot-sdk/issues/598). +Propose a way of randomly permuting the availability chunk indices assigned to validators for a given core and relay +chain block, in the context of +[recovering available data from systematic chunks](https://github.com/paritytech/polkadot-sdk/issues/598). ## Motivation -The relay chain node must have a deterministic way of evenly distributing the first ~(N_VALIDATORS / 3) systematic availability chunks to different validators, -based on the session, relay chain block number and core. -This is needed in order to optimise network load distribution as evenly as possible during availability recovery. +Currently, the ValidatorIndex is always identical to the ChunkIndex. Since the validator array is only shuffled once +per session, naively using the ValidatorIndex as the ChunkIndex would pose an unreasonable stress on the first N/3 +validators during an entire session, when favouring availability recovery from systematic chunks. + +Therefore, the relay chain node needs a deterministic way of evenly distributing the first ~(N_VALIDATORS / 3) +systematic availability chunks to different validators, based on the session, relay chain block number and core. +The main purpose is to ensure fair distribution of network bandwidth usage for availability recovery in general and in +particular for systematic chunk holders. ## Stakeholders @@ -25,27 +31,39 @@ Relay chain node core developers. ### Systematic erasure codes -An erasure coding algorithm is considered systematic if it preserves the original unencoded data as part of the resulting code. +An erasure coding algorithm is considered systematic if it preserves the original unencoded data as part of the +resulting code. [The implementation of the erasure coding algorithm used for polkadot's availability data](https://github.com/paritytech/reed-solomon-novelpoly) is systematic. Roughly speaking, the first N_VALIDATORS/3 -chunks of data can be cheaply concatenated to retrieve the original data, without running the resource-intensive and time-consuming decoding algorithm. +chunks of data can be cheaply concatenated to retrieve the original data, without running the resource-intensive and +time-consuming reconstruction algorithm. -### Availability recovery from systematic chunks +### Availability recovery now -As part of the effort of [increasing polkadot's resource efficiency, scalability and performance](https://github.com/paritytech/roadmap/issues/26), work is under way to -modify the Availability Recovery subsystem by leveraging systematic chunks. See [this comment](https://github.com/paritytech/polkadot-sdk/issues/598#issuecomment-1792007099) -for preliminary performance results. +At this moment, the availability recovery process looks different based on the estimated size of the available data: -In this scheme, the relay chain node will first attempt to retrieve the N/3 systematic chunks from the validators that should hold them, -before falling back to recovering from regular chunks, as before. +(a) for small PoVs (up to 128 Kib), sequentially try requesting the unencoded data from the backing group, in a random +order. If this fails, fallback to option (b). -The problem that this RFC aims to address is that, currently, the ValidatorIndex is always identical to the ChunkIndex. -Since the validator array is only shuffled once per session, naively using the ValidatorIndex as the ChunkIndex -would pose an unreasonable stress on the first N/3 validators during an entire session. +(b) for large PoVs (over 128 Kib), launch N parallel requests for the erasure coded chunks (currently, N has an upper +limit of 50), until enough chunks were recovered. Validators are tried in a random order. Then, reconstruct the +original data. + +### Availability recovery from systematic chunks + +As part of the effort of +[increasing polkadot's resource efficiency, scalability and performance](https://github.com/paritytech/roadmap/issues/26), +work is under way to modify the Availability Recovery subsystem by leveraging systematic chunks. See +[this comment](https://github.com/paritytech/polkadot-sdk/issues/598#issuecomment-1792007099) for preliminary +performance results. + +In this scheme, the relay chain node will first attempt to retrieve the N/3 systematic chunks from the validators that +should hold them, before falling back to recovering from regular chunks, as before. ### Chunk assignment function #### Properties -The function that decides the chunk index for a validator should be parametrised by at least `(validator_index, session_index, block_number, core_index)` +The function that decides the chunk index for a validator should be parametrised by at least +`(validator_index, session_index, block_number, core_index)` and have the following properties: 1. deterministic 1. pseudo-random @@ -91,55 +109,63 @@ for retrieving chunk indices in bulk for all validators at a given block and cor #### Upgrade path -Considering that the Validator->Chunk mapping is critical to para consensus, the change needs to be enacted atomically via -governance, only after all validators have upgraded the node to a version that is aware of this mapping. -It needs to be explicitly stated that after the runtime upgrade and governance enactment, validators that run older client versions that don't -support this mapping will not be able to participate in parachain consensus. +Considering that the Validator->Chunk mapping is critical to para consensus, the change needs to be enacted atomically +via governance, only after all validators have upgraded the node to a version that is aware of this mapping. +It needs to be explicitly stated that after the runtime upgrade and governance enactment, validators that run older +client versions that don't support this mapping will not be able to participate in parachain consensus. ### Getting access to core_index in different subsystems -Availability-recovery can currently be triggered by three actors: -1. `ApprovalVoting` subsystem of the relay chain validator. -2. `pov_recovery` task of collators -3. `DisputeCoordinator` subsystem of the relay chain validator. +Availability-recovery can currently be triggered by the following steps in the polkadot protocol: +1. During the approval voting process. +1. By other collators of the same parachain +3. During disputes. -The one parameter of the assignment function that poses problems to some subsystems is the `core_index`. -The `core_index` refers to the index of the core that the candidate was occupying while it was pending availability (from backing to inclusion). +The `core_index` refers to the index of the core that the candidate was occupying while it was pending availability +(from backing to inclusion). +Getting the right core index for a candidate can prove troublesome. Here's a breakdown of how different parts of the +node implementation can get access to this data: -1. The `ApprovalVoting` subsystem starts the approval process on a candidate as a result to seeing a `CandidateIncluded` event. -This event also contains the core index that the candidate is leaving, so getting access to the core index is not an issue. -1. The `pov_recovery` task of the collators starts availability recovery in response to a `CandidateBacked` event, which enables -easy access to the core index the candidate started occupying. +1. The approval-voting process for a candidate begins after observing that the candidate was included. Therefore, the +node has easy access to the block where the candidate got included (and also the core that it occupied). +1. The `pov_recovery` task of the collators starts availability recovery in response to noticing a candidate getting +backed, which enables easy access to the core index the candidate started occupying. 1. Disputes may be initiated on a number of occasions: - 3.a. is initiated by the current node as a result of finding an invalid candidate while doing approval work. In this case, - availability-recovery is not needed, since the validator already issued their vote. + 3.a. is initiated by the validator as a result of finding an invalid candidate while participating in the + approval-voting protocol. In this case, availability-recovery is not needed, since the validator already issued their + vote. - 3.b is initiated by noticing dispute votes recorded as a result of chain scraping. In this case, the subsystem - assumes it already has a copy of the `CandidateReceipt` as a result of scraping `CandidateBacked` events. The logic can be modified - to also record core indices. + 3.b is initiated by the validator noticing dispute votes recorded on-chain. In this case, we can safely + assume that the backing event for that candidate has been recorded and kept in memory. - 3.c is initiated as a result of getting a dispute statement from another validator. It is possible that the dispute is happening on - a fork that was not yet imported by this validator, so the subsystem will not always have access to the `CandidateBacked` event - recorded by the chain scraper. + 3.c is initiated as a result of getting a dispute statement from another validator. It is possible that the dispute + is happening on a fork that was not yet imported by this validator, so the subsystem may not have seen this candidate + being backed. As a solution to 3.c, a new version for the disputes request-response networking protocol will be added. This message type will include the relay block hash where the candidate was included. This information will be used in order to query the runtime API and retrieve the core index that the candidate was occupying. -The usage of the systematic data availability recovery feature will also be subject to all nodes using the V2 disputes networking protocol. +The usage of the systematic data availability recovery feature will also be subject to all nodes using the V2 disputes +networking protocol. #### Alternatives to using core_index As an alternative to core_index, the `ParaId` could be used. It has the advantage of being readily available in the -`CandidateReceipt`, which would enable the dispute communication protocol to not change and would simplify the implementation. -However, in the context of [CoreJam](https://github.com/polkadot-fellows/RFCs/pull/31), `ParaId`s will no longer exist (at least not in their current form). +`CandidateReceipt`, which would enable the dispute communication protocol to not change and would simplify the +implementation. +However, in the context of [CoreJam](https://github.com/polkadot-fellows/RFCs/pull/31), `ParaId`s will no longer exist +(at least not in their current form). + +Using the candidate hash as a random seed for a shuffle is another option. ## Drawbacks Has a fair amount of technical complexity involved: -- Introduces another runtime API that is going to be issued by multiple subsystems. With adequate client-side caching, this should be acceptable. +- Introduces another runtime API that is going to be issued by multiple subsystems. With adequate client-side caching, +this should be acceptable. - Requires a networking protocol upgrade on the disputes request-response protocol @@ -152,9 +178,12 @@ This proposal doesn't affect security or privacy. ### Performance -This is a necessary optimisation, as reed-solomon erasure coding has proven to be a top consumer of CPU time in polkadot when scaling the parachain count. +This is a necessary DA optimisation, as reed-solomon erasure coding has proven to be a top consumer of CPU time in +polkadot as we scale up the parachain block size and number of availability cores. -With this optimisation, preliminary performance results show that CPU time used for reed-solomon coding can be halved and total POV recovery time decrease by 80% for large POVs. See more [here](https://github.com/paritytech/polkadot-sdk/issues/598#issuecomment-1792007099). +With this optimisation, preliminary performance results show that CPU time used for reed-solomon coding can be halved +and total POV recovery time decrease by 80% for large POVs. See more +[here](https://github.com/paritytech/polkadot-sdk/issues/598#issuecomment-1792007099). ### Ergonomics @@ -163,17 +192,21 @@ Not applicable. ### Compatibility This is a breaking change. See "upgrade path" section above. -All validators need to have upgraded their node versions before the feature will be enabled via a runtime upgrade and governance call. +All validators need to have upgraded their node versions before the feature will be enabled via a runtime upgrade and +governance call. ## Prior Art and References -See comments on the [tracking issue](https://github.com/paritytech/polkadot-sdk/issues/598) and the [in-progress PR](https://github.com/paritytech/polkadot-sdk/pull/1644) +See comments on the [tracking issue](https://github.com/paritytech/polkadot-sdk/issues/598) and the +[in-progress PR](https://github.com/paritytech/polkadot-sdk/pull/1644) ## Unresolved Questions -- Is it a future-proof idea to utilise the core index as a parameter of the chunk index compute function? Is there a better alternative that avoid complicating the implementation? +- Is it a future-proof idea to utilise the core index as a parameter of the chunk index compute function? +Is there a better alternative that avoid complicating the implementation? - Is there a better upgrade path that would preserve backwards compatibility? ## Future Directions and Related Material -This enables future optimisations for the performance of availability recovery, such as retrieving batched systematic chunks from backers/approval-checkers. +This enables future optimisations for the performance of availability recovery, such as retrieving batched systematic +chunks from backers/approval-checkers. From a3ccf05553317c6107edbb9ae6d6116ae5172e9c Mon Sep 17 00:00:00 2001 From: alindima Date: Mon, 13 Nov 2023 16:00:51 +0200 Subject: [PATCH 03/11] fix --- text/0047-random-assignment-of-availability-chunks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0047-random-assignment-of-availability-chunks.md b/text/0047-random-assignment-of-availability-chunks.md index 8fa456fc7..1c4a625d1 100644 --- a/text/0047-random-assignment-of-availability-chunks.md +++ b/text/0047-random-assignment-of-availability-chunks.md @@ -114,7 +114,7 @@ via governance, only after all validators have upgraded the node to a version th It needs to be explicitly stated that after the runtime upgrade and governance enactment, validators that run older client versions that don't support this mapping will not be able to participate in parachain consensus. -### Getting access to core_index in different subsystems +### Getting access to core_index Availability-recovery can currently be triggered by the following steps in the polkadot protocol: 1. During the approval voting process. @@ -164,7 +164,7 @@ Using the candidate hash as a random seed for a shuffle is another option. Has a fair amount of technical complexity involved: -- Introduces another runtime API that is going to be issued by multiple subsystems. With adequate client-side caching, +- Introduces another runtime API that is going to be queried by multiple subsystems. With adequate client-side caching, this should be acceptable. - Requires a networking protocol upgrade on the disputes request-response protocol From a196ddc78a4a8e4642c3e19e3adae52d527c54e7 Mon Sep 17 00:00:00 2001 From: alindima Date: Tue, 14 Nov 2023 10:37:00 +0200 Subject: [PATCH 04/11] address some more comments --- ...andom-assignment-of-availability-chunks.md | 105 +++++++++++++----- 1 file changed, 80 insertions(+), 25 deletions(-) diff --git a/text/0047-random-assignment-of-availability-chunks.md b/text/0047-random-assignment-of-availability-chunks.md index 1c4a625d1..837f4d0f1 100644 --- a/text/0047-random-assignment-of-availability-chunks.md +++ b/text/0047-random-assignment-of-availability-chunks.md @@ -3,14 +3,15 @@ | | | | --------------- | ------------------------------------------------------------------------------------------- | | **Start Date** | 03 November 2023 | -| **Description** | An evenly-distributing indirection layer between availability chunks and validators . | +| **Description** | An evenly-distributing indirection layer between availability chunks and validators. | | **Authors** | Alin Dima | ## Summary Propose a way of randomly permuting the availability chunk indices assigned to validators for a given core and relay chain block, in the context of -[recovering available data from systematic chunks](https://github.com/paritytech/polkadot-sdk/issues/598). +[recovering available data from systematic chunks](https://github.com/paritytech/polkadot-sdk/issues/598), with the +purpose of fairly distributing network bandwidth usage. ## Motivation @@ -33,9 +34,36 @@ Relay chain node core developers. An erasure coding algorithm is considered systematic if it preserves the original unencoded data as part of the resulting code. -[The implementation of the erasure coding algorithm used for polkadot's availability data](https://github.com/paritytech/reed-solomon-novelpoly) is systematic. Roughly speaking, the first N_VALIDATORS/3 -chunks of data can be cheaply concatenated to retrieve the original data, without running the resource-intensive and -time-consuming reconstruction algorithm. +[The implementation of the erasure coding algorithm used for polkadot's availability data](https://github.com/paritytech/reed-solomon-novelpoly) is systematic. +Roughly speaking, the first N_VALIDATORS/3 chunks of data can be cheaply concatenated to retrieve the original data, +without running the resource-intensive and time-consuming reconstruction algorithm. + +Here's the concatenation procedure of systematic chunks for polkadot's erasure coding algorithm +(minus error handling, for briefness): +```rust +pub fn reconstruct_from_systematic( + n_validators: usize, + chunks: Vec<&[u8]>, +) -> T { + let mut threshold = (n_validators - 1) / 3; + if !is_power_of_two(threshold) { + threshold = next_lower_power_of_2(threshold); + } + + let shard_len = chunks.iter().next().unwrap().len(); + + let mut systematic_bytes = Vec::with_capacity(shard_len * kpow2); + + for i in (0..shard_len).step_by(2) { + for chunk in chunks.iter().take(kpow2) { + systematic_bytes.push(chunk[i]); + systematic_bytes.push(chunk[i + 1]); + } + } + + Decode::decode(&mut &systematic_bytes[..]).map_err(|err| Error::Decode(err)) +} +``` ### Availability recovery now @@ -56,13 +84,13 @@ work is under way to modify the Availability Recovery subsystem by leveraging sy [this comment](https://github.com/paritytech/polkadot-sdk/issues/598#issuecomment-1792007099) for preliminary performance results. -In this scheme, the relay chain node will first attempt to retrieve the N/3 systematic chunks from the validators that +In this scheme, the relay chain node will first attempt to retrieve the ~N/3 systematic chunks from the validators that should hold them, before falling back to recovering from regular chunks, as before. ### Chunk assignment function #### Properties -The function that decides the chunk index for a validator should be parametrised by at least +The function that decides the chunk index for a validator should be parameterized by at least `(validator_index, session_index, block_number, core_index)` and have the following properties: 1. deterministic @@ -73,39 +101,66 @@ the function should describe a random permutation of the chunk indices 1. considering `session_index` and `block_number` as fixed arguments, the validators that map to the first N/3 chunk indices should have as little overlap as possible for different cores. -#### Proposed function and runtime API +In other words we want a uniformly distributed, deterministic mapping from `ValidatorIndex` to `ChunkIndex` per block per core. + +#### Proposed runtime API Pseudocode: ```rust pub fn get_chunk_index( - n_validators: u32, - validator_index: ValidatorIndex, - session_index: SessionIndex, - block_number: BlockNumber, - core_index: CoreIndex + n_validators: u32, + validator_index: ValidatorIndex, + session_index: SessionIndex, + block_number: BlockNumber, + core_index: CoreIndex ) -> ChunkIndex { - let threshold = systematic_threshold(n_validators); // Roughly n_validators/3 - let seed = derive_seed(session_index, block_number); - let mut rng: ChaCha8Rng = SeedableRng::from_seed(seed); - let mut chunk_indices: Vec = (0..n_validators).map(Into::into).collect(); - chunk_indices.shuffle(&mut rng); - - let core_start_pos = threshold * core_index.0; - return chunk_indices[(core_start_pos + validator_index) % n_validators] + let threshold = systematic_threshold(n_validators); // Roughly n_validators/3 + let seed = derive_seed(session_index, block_number); + let mut rng: ChaCha8Rng = SeedableRng::from_seed(seed); + let mut chunk_indices: Vec = (0..n_validators).map(Into::into).collect(); + chunk_indices.shuffle(&mut rng); + + let core_start_pos = threshold * core_index.0; + + chunk_indices[(core_start_pos + validator_index) % n_validators] } ``` The function should be implemented as a runtime API, because: -1. it's critical to the consensus protocol that all validators have a common view of the Validator->Chunk mapping. 1. it enables further atomic changes to the shuffling algorithm. 1. it enables alternative client implementations (in other languages) to use it -1. mitigates the problem of third-party libraries changing the implementations of the `ChaCha8Rng` or the `rand::shuffle` -that could be introduced in further versions, which would stall parachains. This would be quite an "easy" attack. +1. considering how critical it is for parachain consensus that all validators have a common view of the Validator->Chunk +mapping, this mitigates the problem of third-party libraries changing the implementations of the `ChaCha8Rng` or the `rand::shuffle` +that could be introduced in further versions. This would stall parachains if only a portion of validators upgraded the node. Additionally, so that client code is able to efficiently get the mapping from the runtime, another API will be added -for retrieving chunk indices in bulk for all validators at a given block and core. +for retrieving chunk indices in bulk for all validators at a given block and core: + +```rust +pub fn get_chunk_indices( + n_validators: u32, + session_index: SessionIndex, + block_number: BlockNumber, + core_index: CoreIndex +) -> Vec { + let threshold = systematic_threshold(n_validators); // Roughly n_validators/3 + let seed = derive_seed(session_index, block_number); + let mut rng: ChaCha8Rng = SeedableRng::from_seed(seed); + let mut chunk_indices: Vec = (0..n_validators).map(Into::into).collect(); + chunk_indices.shuffle(&mut rng); + + let core_start_pos = threshold * core_index.0; + + chunk_indices + .into_iter() + .cycle() + .skip(core_start_pos) + .take(n_validators) + .collect() +} +``` #### Upgrade path From c57a8b6f2b44e397fc440ecc8a9995dab57d80a1 Mon Sep 17 00:00:00 2001 From: alindima Date: Tue, 14 Nov 2023 13:03:35 +0200 Subject: [PATCH 05/11] rewrite proposal using ParaId instead of core_index --- ...andom-assignment-of-availability-chunks.md | 189 ++++++++++-------- 1 file changed, 103 insertions(+), 86 deletions(-) diff --git a/text/0047-random-assignment-of-availability-chunks.md b/text/0047-random-assignment-of-availability-chunks.md index 837f4d0f1..8135881d6 100644 --- a/text/0047-random-assignment-of-availability-chunks.md +++ b/text/0047-random-assignment-of-availability-chunks.md @@ -20,7 +20,7 @@ per session, naively using the ValidatorIndex as the ChunkIndex would pose an un validators during an entire session, when favouring availability recovery from systematic chunks. Therefore, the relay chain node needs a deterministic way of evenly distributing the first ~(N_VALIDATORS / 3) -systematic availability chunks to different validators, based on the session, relay chain block number and core. +systematic availability chunks to different validators, based on the session, relay chain block and core. The main purpose is to ensure fair distribution of network bandwidth usage for availability recovery in general and in particular for systematic chunk holders. @@ -46,9 +46,9 @@ pub fn reconstruct_from_systematic( chunks: Vec<&[u8]>, ) -> T { let mut threshold = (n_validators - 1) / 3; - if !is_power_of_two(threshold) { - threshold = next_lower_power_of_2(threshold); - } + if !is_power_of_two(threshold) { + threshold = next_lower_power_of_2(threshold); + } let shard_len = chunks.iter().next().unwrap().len(); @@ -65,9 +65,17 @@ pub fn reconstruct_from_systematic( } ``` +In a nutshell, it performs a column-wise concatenation with 2-bit chunks. + ### Availability recovery now -At this moment, the availability recovery process looks different based on the estimated size of the available data: +According to the [polkadot protocol spec](https://spec.polkadot.network/chapter-anv#sect-candidate-recovery): + +> A validator should request chunks by picking peers randomly and must recover at least `f+1` chunks, where +`n=3f+k` and `k in {1,2,3}`. + +For parity's polkadot node implementation, the process was further optimised. At this moment, it works differently based +on the estimated size of the available data: (a) for small PoVs (up to 128 Kib), sequentially try requesting the unencoded data from the backing group, in a random order. If this fails, fallback to option (b). @@ -80,7 +88,7 @@ original data. As part of the effort of [increasing polkadot's resource efficiency, scalability and performance](https://github.com/paritytech/roadmap/issues/26), -work is under way to modify the Availability Recovery subsystem by leveraging systematic chunks. See +work is under way to modify the Availability Recovery protocol by leveraging systematic chunks. See [this comment](https://github.com/paritytech/polkadot-sdk/issues/598#issuecomment-1792007099) for preliminary performance results. @@ -90,68 +98,75 @@ should hold them, before falling back to recovering from regular chunks, as befo ### Chunk assignment function #### Properties + The function that decides the chunk index for a validator should be parameterized by at least -`(validator_index, session_index, block_number, core_index)` +`(validator_index, relay_parent, para_id)` and have the following properties: 1. deterministic 1. pseudo-random 1. relatively quick to compute and resource-efficient. -1. when considering the other params besides `validator_index` as fixed, -the function should describe a random permutation of the chunk indices -1. considering `session_index` and `block_number` as fixed arguments, the validators that map to the first N/3 chunk indices should -have as little overlap as possible for different cores. +1. when considering the other params besides `validator_index` as fixed, the function should describe a random permutation +of the chunk indices +1. considering `relay_parent` as a fixed argument, the validators that map to the first N/3 chunk indices should +have as little overlap as possible for different paras scheduled on that relay parent. -In other words we want a uniformly distributed, deterministic mapping from `ValidatorIndex` to `ChunkIndex` per block per core. +In other words, we want a uniformly distributed, deterministic mapping from `ValidatorIndex` to `ChunkIndex` per block +per scheduled para. #### Proposed runtime API +The mapping function should be implemented as a runtime API, because: + +1. it enables further atomic changes to the shuffling algorithm. +1. it enables alternative client implementations (in other languages) to use it +1. considering how critical it is for parachain consensus that all validators have a common view of the Validator->Chunk +mapping, this mitigates the problem of third-party libraries changing the implementations of the `ChaCha8Rng` or the `rand::shuffle` +that could be introduced in further versions. This would stall parachains if only a portion of validators upgraded the node. + + Pseudocode: ```rust pub fn get_chunk_index( n_validators: u32, validator_index: ValidatorIndex, - session_index: SessionIndex, - block_number: BlockNumber, - core_index: CoreIndex + relay_parent: Hash, + para_id: ParaId ) -> ChunkIndex { let threshold = systematic_threshold(n_validators); // Roughly n_validators/3 - let seed = derive_seed(session_index, block_number); + let seed = derive_seed(relay_parent); let mut rng: ChaCha8Rng = SeedableRng::from_seed(seed); let mut chunk_indices: Vec = (0..n_validators).map(Into::into).collect(); chunk_indices.shuffle(&mut rng); - let core_start_pos = threshold * core_index.0; + let seed = derive_seed(hash(para_id)); + let mut rng: ChaCha8Rng = SeedableRng::from_seed(seed); + + let core_start_pos = rng.gen_range(0..n_validators); chunk_indices[(core_start_pos + validator_index) % n_validators] } ``` -The function should be implemented as a runtime API, because: - -1. it enables further atomic changes to the shuffling algorithm. -1. it enables alternative client implementations (in other languages) to use it -1. considering how critical it is for parachain consensus that all validators have a common view of the Validator->Chunk -mapping, this mitigates the problem of third-party libraries changing the implementations of the `ChaCha8Rng` or the `rand::shuffle` -that could be introduced in further versions. This would stall parachains if only a portion of validators upgraded the node. - Additionally, so that client code is able to efficiently get the mapping from the runtime, another API will be added for retrieving chunk indices in bulk for all validators at a given block and core: ```rust pub fn get_chunk_indices( n_validators: u32, - session_index: SessionIndex, - block_number: BlockNumber, - core_index: CoreIndex + relay_parent: Hash, + para_id: ParaId ) -> Vec { let threshold = systematic_threshold(n_validators); // Roughly n_validators/3 - let seed = derive_seed(session_index, block_number); + let seed = derive_seed(relay_parent); let mut rng: ChaCha8Rng = SeedableRng::from_seed(seed); let mut chunk_indices: Vec = (0..n_validators).map(Into::into).collect(); chunk_indices.shuffle(&mut rng); - let core_start_pos = threshold * core_index.0; + let seed = derive_seed(hash(para_id)); + let mut rng: ChaCha8Rng = SeedableRng::from_seed(seed); + + let core_start_pos = rng.gen_range(0..n_validators); chunk_indices .into_iter() @@ -169,60 +184,18 @@ via governance, only after all validators have upgraded the node to a version th It needs to be explicitly stated that after the runtime upgrade and governance enactment, validators that run older client versions that don't support this mapping will not be able to participate in parachain consensus. -### Getting access to core_index - -Availability-recovery can currently be triggered by the following steps in the polkadot protocol: -1. During the approval voting process. -1. By other collators of the same parachain -3. During disputes. - -The `core_index` refers to the index of the core that the candidate was occupying while it was pending availability -(from backing to inclusion). -Getting the right core index for a candidate can prove troublesome. Here's a breakdown of how different parts of the -node implementation can get access to this data: - -1. The approval-voting process for a candidate begins after observing that the candidate was included. Therefore, the -node has easy access to the block where the candidate got included (and also the core that it occupied). -1. The `pov_recovery` task of the collators starts availability recovery in response to noticing a candidate getting -backed, which enables easy access to the core index the candidate started occupying. -1. Disputes may be initiated on a number of occasions: - - 3.a. is initiated by the validator as a result of finding an invalid candidate while participating in the - approval-voting protocol. In this case, availability-recovery is not needed, since the validator already issued their - vote. - - 3.b is initiated by the validator noticing dispute votes recorded on-chain. In this case, we can safely - assume that the backing event for that candidate has been recorded and kept in memory. - - 3.c is initiated as a result of getting a dispute statement from another validator. It is possible that the dispute - is happening on a fork that was not yet imported by this validator, so the subsystem may not have seen this candidate - being backed. - -As a solution to 3.c, a new version for the disputes request-response networking protocol will be added. -This message type will include the relay block hash where the candidate was included. This information will be used -in order to query the runtime API and retrieve the core index that the candidate was occupying. - -The usage of the systematic data availability recovery feature will also be subject to all nodes using the V2 disputes -networking protocol. - -#### Alternatives to using core_index - -As an alternative to core_index, the `ParaId` could be used. It has the advantage of being readily available in the -`CandidateReceipt`, which would enable the dispute communication protocol to not change and would simplify the -implementation. -However, in the context of [CoreJam](https://github.com/polkadot-fellows/RFCs/pull/31), `ParaId`s will no longer exist -(at least not in their current form). - -Using the candidate hash as a random seed for a shuffle is another option. +Additionally, an error will be logged when starting a validator with an older version, after the runtime was upgraded and the feature enabled. ## Drawbacks -Has a fair amount of technical complexity involved: - -- Introduces another runtime API that is going to be queried by multiple subsystems. With adequate client-side caching, -this should be acceptable. - -- Requires a networking protocol upgrade on the disputes request-response protocol +- In terms of guaranteeing even load distribution, a simpler function that chooses the per-core start position in the +shuffle as `threshold * core_index` would likely perform better, but considering that the core_index is not part of the +CandidateReceipt, the implementation would be too complicated. More details in [Appendix A](#appendix-a). +- Considering future protocol changes that aim to generalise the work polkadot is doing (like CoreJam), `ParaId`s may be +removed from the protocol, in favour of more generic primitives. In that case, `ParaId`s in the availability recovery +process should be replaced with a similar identifier. It's important to note that the implementation is greatly simplified +if this identifier is part of the `CandidateReceipt` or the future analogous data structure. +- It's a breaking change that requires most validators to be upgrade their node version. ## Testing, Security, and Privacy @@ -233,8 +206,8 @@ This proposal doesn't affect security or privacy. ### Performance -This is a necessary DA optimisation, as reed-solomon erasure coding has proven to be a top consumer of CPU time in -polkadot as we scale up the parachain block size and number of availability cores. +This is a necessary data availability optimisation, as reed-solomon erasure coding has proven to be a top consumer of +CPU time in polkadot as we scale up the parachain block size and number of availability cores. With this optimisation, preliminary performance results show that CPU time used for reed-solomon coding can be halved and total POV recovery time decrease by 80% for large POVs. See more @@ -246,7 +219,7 @@ Not applicable. ### Compatibility -This is a breaking change. See "upgrade path" section above. +This is a breaking change. See [upgrade path](#upgrade-path) section above. All validators need to have upgraded their node versions before the feature will be enabled via a runtime upgrade and governance call. @@ -257,11 +230,55 @@ See comments on the [tracking issue](https://github.com/paritytech/polkadot-sdk/ ## Unresolved Questions -- Is it a future-proof idea to utilise the core index as a parameter of the chunk index compute function? -Is there a better alternative that avoid complicating the implementation? +- Is it the best option to embed the mapping function in the runtime? - Is there a better upgrade path that would preserve backwards compatibility? +- Is usage of `ParaId` the best choice for spreading out the network load during systematic chunk recovery within the +same block? ## Future Directions and Related Material This enables future optimisations for the performance of availability recovery, such as retrieving batched systematic chunks from backers/approval-checkers. + +## Appendix A + +This appendix explores alternatives to using the `ParaId` as the factor by which availability chunk indices are +distributed to validators within the same relay chain block, and why they weren't chosen. + +### Core index + +Here, `core_index` refers to the index of the core that a candidate was occupying while it was pending availability +(from backing to inclusion). + +Availability-recovery can currently be triggered by the following phases in the polkadot protocol: +1. During the approval voting process. +1. By other collators of the same parachain. +1. During disputes. + +Getting the right core index for a candidate is troublesome. Here's a breakdown of how different parts of the +node implementation can get access to it: + +1. The approval-voting process for a candidate begins after observing that the candidate was included. Therefore, the +node has easy access to the block where the candidate got included (and also the core that it occupied). +1. The `pov_recovery` task of the collators starts availability recovery in response to noticing a candidate getting +backed, which enables easy access to the core index the candidate started occupying. +1. Disputes may be initiated on a number of occasions: + + 3.a. is initiated by the validator as a result of finding an invalid candidate while participating in the + approval-voting protocol. In this case, availability-recovery is not needed, since the validator already issued their + vote. + + 3.b is initiated by the validator noticing dispute votes recorded on-chain. In this case, we can safely + assume that the backing event for that candidate has been recorded and kept in memory. + + 3.c is initiated as a result of getting a dispute statement from another validator. It is possible that the dispute + is happening on a fork that was not yet imported by this validator, so the subsystem may not have seen this candidate + being backed. + +A naive attempt of solving 3.c would be to add a new version for the disputes request-response networking protocol. +Blindly passing the core index in the network payload would not work, since there is no way of validating that +the reported core_index was indeed the one occupied by the candidate at the respective relay parent. + +Another attempt could be to include in the message the relay block hash where the candidate was included. +This information would be used in order to query the runtime API and retrieve the core index that the candidate was +occupying. However, considering it's part of an unimported fork, the validator cannot call a runtime API on that block. \ No newline at end of file From b3a4868b4cb2f0296744df45e2719cfdf0881a07 Mon Sep 17 00:00:00 2001 From: alindima Date: Tue, 14 Nov 2023 13:07:31 +0200 Subject: [PATCH 06/11] rename core_start_pos to para_start_pos --- ...0047-random-assignment-of-availability-chunks.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/text/0047-random-assignment-of-availability-chunks.md b/text/0047-random-assignment-of-availability-chunks.md index 8135881d6..1d1f81e6b 100644 --- a/text/0047-random-assignment-of-availability-chunks.md +++ b/text/0047-random-assignment-of-availability-chunks.md @@ -141,10 +141,9 @@ pub fn get_chunk_index( let seed = derive_seed(hash(para_id)); let mut rng: ChaCha8Rng = SeedableRng::from_seed(seed); - - let core_start_pos = rng.gen_range(0..n_validators); + let para_start_pos = rng.gen_range(0..n_validators); - chunk_indices[(core_start_pos + validator_index) % n_validators] + chunk_indices[(para_start_pos + validator_index) % n_validators] } ``` @@ -165,13 +164,13 @@ pub fn get_chunk_indices( let seed = derive_seed(hash(para_id)); let mut rng: ChaCha8Rng = SeedableRng::from_seed(seed); - - let core_start_pos = rng.gen_range(0..n_validators); - + + let para_start_pos = rng.gen_range(0..n_validators); + chunk_indices .into_iter() .cycle() - .skip(core_start_pos) + .skip(para_start_pos) .take(n_validators) .collect() } From 28511f68b53fba7ecf2c9dfa183bfc81bbdd9795 Mon Sep 17 00:00:00 2001 From: alindima Date: Tue, 14 Nov 2023 18:09:44 +0200 Subject: [PATCH 07/11] fix bit->byte and kpow2->threshold --- text/0047-random-assignment-of-availability-chunks.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0047-random-assignment-of-availability-chunks.md b/text/0047-random-assignment-of-availability-chunks.md index 1d1f81e6b..648303c0b 100644 --- a/text/0047-random-assignment-of-availability-chunks.md +++ b/text/0047-random-assignment-of-availability-chunks.md @@ -52,10 +52,10 @@ pub fn reconstruct_from_systematic( let shard_len = chunks.iter().next().unwrap().len(); - let mut systematic_bytes = Vec::with_capacity(shard_len * kpow2); + let mut systematic_bytes = Vec::with_capacity(shard_len * threshold); for i in (0..shard_len).step_by(2) { - for chunk in chunks.iter().take(kpow2) { + for chunk in chunks.iter().take(threshold) { systematic_bytes.push(chunk[i]); systematic_bytes.push(chunk[i + 1]); } @@ -65,7 +65,7 @@ pub fn reconstruct_from_systematic( } ``` -In a nutshell, it performs a column-wise concatenation with 2-bit chunks. +In a nutshell, it performs a column-wise concatenation with 2-byte chunks. ### Availability recovery now From 303368bd1452fac99b28a41ac1aebff1762684c4 Mon Sep 17 00:00:00 2001 From: alindima Date: Tue, 28 Nov 2023 16:59:37 +0200 Subject: [PATCH 08/11] rewrite --- ...0047-assignment-of-availability-chunks.md} | 172 ++++++++++-------- 1 file changed, 101 insertions(+), 71 deletions(-) rename text/{0047-random-assignment-of-availability-chunks.md => 0047-assignment-of-availability-chunks.md} (64%) diff --git a/text/0047-random-assignment-of-availability-chunks.md b/text/0047-assignment-of-availability-chunks.md similarity index 64% rename from text/0047-random-assignment-of-availability-chunks.md rename to text/0047-assignment-of-availability-chunks.md index 648303c0b..b98e7af1e 100644 --- a/text/0047-random-assignment-of-availability-chunks.md +++ b/text/0047-assignment-of-availability-chunks.md @@ -1,4 +1,4 @@ -# RFC-0047: Random assignment of availability chunks to validators +# RFC-0047: Assignment of availability chunks to validators | | | | --------------- | ------------------------------------------------------------------------------------------- | @@ -8,7 +8,7 @@ ## Summary -Propose a way of randomly permuting the availability chunk indices assigned to validators for a given core and relay +Propose a way of permuting the availability chunk indices assigned to validators for a given core and relay chain block, in the context of [recovering available data from systematic chunks](https://github.com/paritytech/polkadot-sdk/issues/598), with the purpose of fairly distributing network bandwidth usage. @@ -20,7 +20,7 @@ per session, naively using the ValidatorIndex as the ChunkIndex would pose an un validators during an entire session, when favouring availability recovery from systematic chunks. Therefore, the relay chain node needs a deterministic way of evenly distributing the first ~(N_VALIDATORS / 3) -systematic availability chunks to different validators, based on the session, relay chain block and core. +systematic availability chunks to different validators, based on the relay chain block and core. The main purpose is to ensure fair distribution of network bandwidth usage for availability recovery in general and in particular for systematic chunk holders. @@ -61,13 +61,15 @@ pub fn reconstruct_from_systematic( } } - Decode::decode(&mut &systematic_bytes[..]).map_err(|err| Error::Decode(err)) + Decode::decode(&mut &systematic_bytes[..]).unwrap() } ``` In a nutshell, it performs a column-wise concatenation with 2-byte chunks. +The output could be zero-padded at the end, so scale decoding must be aware of the expected length in bytes and ignore +trailing zeros. -### Availability recovery now +### Availability recovery at present According to the [polkadot protocol spec](https://spec.polkadot.network/chapter-anv#sect-candidate-recovery): @@ -84,6 +86,9 @@ order. If this fails, fallback to option (b). limit of 50), until enough chunks were recovered. Validators are tried in a random order. Then, reconstruct the original data. +All options require that after reconstruction, validators then re-encode the data and re-create the erasure chunks trie +in order to check the erasure root. + ### Availability recovery from systematic chunks As part of the effort of @@ -95,34 +100,32 @@ performance results. In this scheme, the relay chain node will first attempt to retrieve the ~N/3 systematic chunks from the validators that should hold them, before falling back to recovering from regular chunks, as before. +A re-encoding step is still needed for verifying the erasure root, so the erasure coding overhead cannot be completely +brought down to 0. + ### Chunk assignment function #### Properties The function that decides the chunk index for a validator should be parameterized by at least -`(validator_index, relay_parent, para_id)` +`(validator_index, relay_parent, core_index)` and have the following properties: 1. deterministic -1. pseudo-random 1. relatively quick to compute and resource-efficient. -1. when considering the other params besides `validator_index` as fixed, the function should describe a random permutation +1. when considering the other params besides `validator_index` as fixed, the function should describe a permutation of the chunk indices 1. considering `relay_parent` as a fixed argument, the validators that map to the first N/3 chunk indices should have as little overlap as possible for different paras scheduled on that relay parent. In other words, we want a uniformly distributed, deterministic mapping from `ValidatorIndex` to `ChunkIndex` per block -per scheduled para. - -#### Proposed runtime API +per core. -The mapping function should be implemented as a runtime API, because: - -1. it enables further atomic changes to the shuffling algorithm. -1. it enables alternative client implementations (in other languages) to use it -1. considering how critical it is for parachain consensus that all validators have a common view of the Validator->Chunk -mapping, this mitigates the problem of third-party libraries changing the implementations of the `ChaCha8Rng` or the `rand::shuffle` -that could be introduced in further versions. This would stall parachains if only a portion of validators upgraded the node. +It's desirable to not embed this function in the runtime, for performance and complexity reasons. +However, this means that the function needs to be kept very simple and with minimal or no external dependencies. +Any change to this function could result in parachains being stalled and needs to be coordinated via a runtime upgrade +or governance call. +#### Proposed function Pseudocode: @@ -130,71 +133,104 @@ Pseudocode: pub fn get_chunk_index( n_validators: u32, validator_index: ValidatorIndex, - relay_parent: Hash, - para_id: ParaId + block_number: BlockNumber, + core_index: CoreIndex ) -> ChunkIndex { let threshold = systematic_threshold(n_validators); // Roughly n_validators/3 - let seed = derive_seed(relay_parent); - let mut rng: ChaCha8Rng = SeedableRng::from_seed(seed); - let mut chunk_indices: Vec = (0..n_validators).map(Into::into).collect(); - chunk_indices.shuffle(&mut rng); + let core_start_pos = abs(core_index - block_number) * threshold; - let seed = derive_seed(hash(para_id)); - let mut rng: ChaCha8Rng = SeedableRng::from_seed(seed); - let para_start_pos = rng.gen_range(0..n_validators); + (core_start_pos + validator_index) % n_validators +} +``` - chunk_indices[(para_start_pos + validator_index) % n_validators] +### Network protocol + +The request-response `/polkadot/req_chunk` protocol will be bumped to a new version (from v1 to v2). +For v1, the request and response payloads are: +```rust +/// Request an availability chunk. +pub struct ChunkFetchingRequest { + /// Hash of candidate we want a chunk for. + pub candidate_hash: CandidateHash, + /// The index of the chunk to fetch. + pub index: ValidatorIndex, +} + +/// Receive a requested erasure chunk. +pub enum ChunkFetchingResponse { + /// The requested chunk data. + Chunk(ChunkResponse), + /// Node was not in possession of the requested chunk. + NoSuchChunk, +} + +/// This omits the chunk's index because it is already known by +/// the requester and by not transmitting it, we ensure the requester is going to use his index +/// value for validating the response, thus making sure he got what he requested. +pub struct ChunkResponse { + /// The erasure-encoded chunk of data belonging to the candidate block. + pub chunk: Vec, + /// Proof for this chunk's branch in the Merkle tree. + pub proof: Proof, } ``` -Additionally, so that client code is able to efficiently get the mapping from the runtime, another API will be added -for retrieving chunk indices in bulk for all validators at a given block and core: +Version 2 will add an `index` field to `ChunkResponse`: ```rust -pub fn get_chunk_indices( - n_validators: u32, - relay_parent: Hash, - para_id: ParaId -) -> Vec { - let threshold = systematic_threshold(n_validators); // Roughly n_validators/3 - let seed = derive_seed(relay_parent); - let mut rng: ChaCha8Rng = SeedableRng::from_seed(seed); - let mut chunk_indices: Vec = (0..n_validators).map(Into::into).collect(); - chunk_indices.shuffle(&mut rng); - - let seed = derive_seed(hash(para_id)); - let mut rng: ChaCha8Rng = SeedableRng::from_seed(seed); - - let para_start_pos = rng.gen_range(0..n_validators); - - chunk_indices - .into_iter() - .cycle() - .skip(para_start_pos) - .take(n_validators) - .collect() +#[derive(Debug, Clone, Encode, Decode)] +pub struct ChunkResponse { + /// The erasure-encoded chunk of data belonging to the candidate block. + pub chunk: Vec, + /// Proof for this chunk's branch in the Merkle tree. + pub proof: Proof, + /// Chunk index. + pub index: ChunkIndex } ``` -#### Upgrade path +An important thing to note is that in version 1, the `ValidatorIndex` value is always equal to the `ChunkIndex`. +Until the feature is enabled, this will also be true for version 2. However, after the feature is enabled, +this will generally not be true. + +The requester will send the request to validator with index `V`. The responder will map the `V` validator index to the +`C` chunk index and respond with the `C`-th chunk. + +The protocol implementation MAY check the returned `ChunkIndex` against the expected mapping to ensure that +it received the right chunk. +In practice, this is desirable during availability-distribution and systematic chunk recovery. However, regular +recovery may not check this index, which is particularly useful when participating in disputes that don't allow +for easy access to the validator->chunk mapping. See [Appendix A](#appendix-a) for more details. + +### Upgrade path + +#### Step 1: Enabling new network protocol +In the beginning, both `/polkadot/req_chunk/1` and `/polkadot/req_chunk/2` will be supported, until all validators and +collators have upgraded to use the new version. V1 will be considered deprecated. +Once all nodes are upgraded, a new release will be cut that removes the v1 protocol. Only once all nodes have upgraded +to this version will step 2 commence. + +#### Step 2: Enabling the new validator->chunk mapping Considering that the Validator->Chunk mapping is critical to para consensus, the change needs to be enacted atomically via governance, only after all validators have upgraded the node to a version that is aware of this mapping. It needs to be explicitly stated that after the runtime upgrade and governance enactment, validators that run older client versions that don't support this mapping will not be able to participate in parachain consensus. -Additionally, an error will be logged when starting a validator with an older version, after the runtime was upgraded and the feature enabled. +Additionally, an error will be logged when starting a validator with an older version, after the runtime was upgraded +and the feature enabled. + +On the other hand, collators will not be required to upgrade, as regular chunk recovery will work as before, granted +that version 1 of the networking protocol has been removed. However, they are encouraged to upgrade in order to take +advantage of the faster systematic recovery. ## Drawbacks -- In terms of guaranteeing even load distribution, a simpler function that chooses the per-core start position in the -shuffle as `threshold * core_index` would likely perform better, but considering that the core_index is not part of the -CandidateReceipt, the implementation would be too complicated. More details in [Appendix A](#appendix-a). -- Considering future protocol changes that aim to generalise the work polkadot is doing (like CoreJam), `ParaId`s may be -removed from the protocol, in favour of more generic primitives. In that case, `ParaId`s in the availability recovery -process should be replaced with a similar identifier. It's important to note that the implementation is greatly simplified -if this identifier is part of the `CandidateReceipt` or the future analogous data structure. -- It's a breaking change that requires most validators to be upgrade their node version. +- Getting access to the `core_index` that used to be occupied by a candidate in some parts of the dispute protocol is +very complicated (See [appendix A](#appendix-a)). This RFC assumes that availability-recovery processes initiated during +disputes will only use regular recovery, as before. This is acceptable since disputes are rare occurrences in practice +and is something that can be optimised later, if need be. +- It's a breaking change that requires all validators and collators to upgrade their node version. ## Testing, Security, and Privacy @@ -229,10 +265,7 @@ See comments on the [tracking issue](https://github.com/paritytech/polkadot-sdk/ ## Unresolved Questions -- Is it the best option to embed the mapping function in the runtime? - Is there a better upgrade path that would preserve backwards compatibility? -- Is usage of `ParaId` the best choice for spreading out the network load during systematic chunk recovery within the -same block? ## Future Directions and Related Material @@ -241,10 +274,7 @@ chunks from backers/approval-checkers. ## Appendix A -This appendix explores alternatives to using the `ParaId` as the factor by which availability chunk indices are -distributed to validators within the same relay chain block, and why they weren't chosen. - -### Core index +This appendix details the intricacies of getting access to the core index of a candidate in parity's polkadot node. Here, `core_index` refers to the index of the core that a candidate was occupying while it was pending availability (from backing to inclusion). @@ -254,7 +284,7 @@ Availability-recovery can currently be triggered by the following phases in the 1. By other collators of the same parachain. 1. During disputes. -Getting the right core index for a candidate is troublesome. Here's a breakdown of how different parts of the +Getting the right core index for a candidate can be troublesome. Here's a breakdown of how different parts of the node implementation can get access to it: 1. The approval-voting process for a candidate begins after observing that the candidate was included. Therefore, the @@ -280,4 +310,4 @@ the reported core_index was indeed the one occupied by the candidate at the resp Another attempt could be to include in the message the relay block hash where the candidate was included. This information would be used in order to query the runtime API and retrieve the core index that the candidate was -occupying. However, considering it's part of an unimported fork, the validator cannot call a runtime API on that block. \ No newline at end of file +occupying. However, considering it's part of an unimported fork, the validator cannot call a runtime API on that block. From 2395237bffbc180d596810853df4d530040457af Mon Sep 17 00:00:00 2001 From: alindima Date: Mon, 4 Dec 2023 14:49:58 +0200 Subject: [PATCH 09/11] address review comments and add more details --- .../0047-assignment-of-availability-chunks.md | 70 +++++++++++++------ 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/text/0047-assignment-of-availability-chunks.md b/text/0047-assignment-of-availability-chunks.md index b98e7af1e..1407679cf 100644 --- a/text/0047-assignment-of-availability-chunks.md +++ b/text/0047-assignment-of-availability-chunks.md @@ -45,13 +45,8 @@ pub fn reconstruct_from_systematic( n_validators: usize, chunks: Vec<&[u8]>, ) -> T { - let mut threshold = (n_validators - 1) / 3; - if !is_power_of_two(threshold) { - threshold = next_lower_power_of_2(threshold); - } - + let threshold = systematic_threshold(n_validators); let shard_len = chunks.iter().next().unwrap().len(); - let mut systematic_bytes = Vec::with_capacity(shard_len * threshold); for i in (0..shard_len).step_by(2) { @@ -63,6 +58,15 @@ pub fn reconstruct_from_systematic( Decode::decode(&mut &systematic_bytes[..]).unwrap() } + +fn systematic_threshold(n_validators: usize) -> usize { + let mut threshold = (n_validators - 1) / 3; + if !is_power_of_two(threshold) { + threshold = next_lower_power_of_2(threshold); + } + + threshold +} ``` In a nutshell, it performs a column-wise concatenation with 2-byte chunks. @@ -103,18 +107,22 @@ should hold them, before falling back to recovering from regular chunks, as befo A re-encoding step is still needed for verifying the erasure root, so the erasure coding overhead cannot be completely brought down to 0. +Not being able to retrieve even one systematic chunk would make systematic reconstruction impossible. Therefore, backers +can be used as a backup to retrieve a couple of missing systematic chunks, before falling back to retrieving regular +chunks. + ### Chunk assignment function #### Properties The function that decides the chunk index for a validator should be parameterized by at least -`(validator_index, relay_parent, core_index)` +`(validator_index, block_number, core_index)` and have the following properties: 1. deterministic 1. relatively quick to compute and resource-efficient. 1. when considering the other params besides `validator_index` as fixed, the function should describe a permutation of the chunk indices -1. considering `relay_parent` as a fixed argument, the validators that map to the first N/3 chunk indices should +1. considering `block_number` as a fixed argument, the validators that map to the first N/3 chunk indices should have as little overlap as possible for different paras scheduled on that relay parent. In other words, we want a uniformly distributed, deterministic mapping from `ValidatorIndex` to `ChunkIndex` per block @@ -145,7 +153,7 @@ pub fn get_chunk_index( ### Network protocol -The request-response `/polkadot/req_chunk` protocol will be bumped to a new version (from v1 to v2). +The request-response `/req_chunk` protocol will be bumped to a new version (from v1 to v2). For v1, the request and response payloads are: ```rust /// Request an availability chunk. @@ -202,34 +210,50 @@ In practice, this is desirable during availability-distribution and systematic c recovery may not check this index, which is particularly useful when participating in disputes that don't allow for easy access to the validator->chunk mapping. See [Appendix A](#appendix-a) for more details. +In any case, the requester MUST verify the chunk's proof using the provided index. + +During availability-recovery, given that the requester may not know (if the mapping is not available) whether the received chunk corresponds to +the requested validator index, it has to keep track of received chunk indices and ignore duplicates. Such duplicates +should be considered the same as an invalid/garbage response (drop it and move on to the next validator - we can't +punish via reputation changes, because we don't know which validator misbehaved). ### Upgrade path #### Step 1: Enabling new network protocol -In the beginning, both `/polkadot/req_chunk/1` and `/polkadot/req_chunk/2` will be supported, until all validators and -collators have upgraded to use the new version. V1 will be considered deprecated. +In the beginning, both `/req_chunk/1` and `/req_chunk/2` will be supported, until all validators and +collators have upgraded to use the new version. V1 will be considered deprecated. During this step, the mapping will +still be 1:1 (`ValidatorIndex` == `ChunkIndex`), regardless of protocol. Once all nodes are upgraded, a new release will be cut that removes the v1 protocol. Only once all nodes have upgraded to this version will step 2 commence. #### Step 2: Enabling the new validator->chunk mapping Considering that the Validator->Chunk mapping is critical to para consensus, the change needs to be enacted atomically -via governance, only after all validators have upgraded the node to a version that is aware of this mapping. -It needs to be explicitly stated that after the runtime upgrade and governance enactment, validators that run older -client versions that don't support this mapping will not be able to participate in parachain consensus. +via governance, only after all validators have upgraded the node to a version that is aware of this mapping, +functionality-wise. +It needs to be explicitly stated that after the governance enactment, validators that run older client versions that +don't support this mapping will not be able to participate in parachain consensus. + +Additionally, an error will be logged when starting a validator with an older version, after the feature was enabled. -Additionally, an error will be logged when starting a validator with an older version, after the runtime was upgraded -and the feature enabled. +On the other hand, collators will not be required to upgrade in this step, as regular chunk recovery will work as before, +granted that version 1 of the networking protocol has been removed. Note that collators only perform +availability-recovery in rare, adversarial scenarios, so it is fine to not optimise for this case and let them upgrade +at their own pace. -On the other hand, collators will not be required to upgrade, as regular chunk recovery will work as before, granted -that version 1 of the networking protocol has been removed. However, they are encouraged to upgrade in order to take -advantage of the faster systematic recovery. +To support enabling this feature via the runtime, we will use the `NodeFeatures` bitfield of the `HostConfiguration` +struct (added in `https://github.com/paritytech/polkadot-sdk/pull/2177`). Adding and enabling a feature +with this scheme does not require a runtime upgrade, but only a referendum that issues a +`Configuration::set_node_feature` extrinsic. Once the feature is enabled and new configuration is live, the +validator->chunk mapping ceases to be a 1:1 mapping and systematic recovery may begin. ## Drawbacks - Getting access to the `core_index` that used to be occupied by a candidate in some parts of the dispute protocol is very complicated (See [appendix A](#appendix-a)). This RFC assumes that availability-recovery processes initiated during disputes will only use regular recovery, as before. This is acceptable since disputes are rare occurrences in practice -and is something that can be optimised later, if need be. +and is something that can be optimised later, if need be. Adding the `core_index` to the `CandidateReceipt` would +mitigate this problem and will likely be needed in the future for CoreJam. +[Related discussion about `CandidateReceipt`](https://forum.polkadot.network/t/pre-rfc-discussion-candidate-receipt-format-v2/3738) - It's a breaking change that requires all validators and collators to upgrade their node version. ## Testing, Security, and Privacy @@ -244,8 +268,8 @@ This proposal doesn't affect security or privacy. This is a necessary data availability optimisation, as reed-solomon erasure coding has proven to be a top consumer of CPU time in polkadot as we scale up the parachain block size and number of availability cores. -With this optimisation, preliminary performance results show that CPU time used for reed-solomon coding can be halved -and total POV recovery time decrease by 80% for large POVs. See more +With this optimisation, preliminary performance results show that CPU time used for reed-solomon coding/decoding can be +halved and total POV recovery time decrease by 80% for large POVs. See more [here](https://github.com/paritytech/polkadot-sdk/issues/598#issuecomment-1792007099). ### Ergonomics @@ -255,7 +279,7 @@ Not applicable. ### Compatibility This is a breaking change. See [upgrade path](#upgrade-path) section above. -All validators need to have upgraded their node versions before the feature will be enabled via a runtime upgrade and +All validators need to have upgraded their node versions before the feature will be enabled via a runtime upgrade governance call. ## Prior Art and References From 90b0a50f35b23c1d139f3010acd7337dfe4f3c63 Mon Sep 17 00:00:00 2001 From: alindima Date: Mon, 8 Jan 2024 14:00:20 +0200 Subject: [PATCH 10/11] remove block_number from chunk rotation function --- .../0047-assignment-of-availability-chunks.md | 88 +++++++------------ 1 file changed, 30 insertions(+), 58 deletions(-) diff --git a/text/0047-assignment-of-availability-chunks.md b/text/0047-assignment-of-availability-chunks.md index 1407679cf..2e4142058 100644 --- a/text/0047-assignment-of-availability-chunks.md +++ b/text/0047-assignment-of-availability-chunks.md @@ -8,8 +8,7 @@ ## Summary -Propose a way of permuting the availability chunk indices assigned to validators for a given core and relay -chain block, in the context of +Propose a way of permuting the availability chunk indices assigned to validators, in the context of [recovering available data from systematic chunks](https://github.com/paritytech/polkadot-sdk/issues/598), with the purpose of fairly distributing network bandwidth usage. @@ -38,40 +37,12 @@ resulting code. Roughly speaking, the first N_VALIDATORS/3 chunks of data can be cheaply concatenated to retrieve the original data, without running the resource-intensive and time-consuming reconstruction algorithm. -Here's the concatenation procedure of systematic chunks for polkadot's erasure coding algorithm -(minus error handling, for briefness): -```rust -pub fn reconstruct_from_systematic( - n_validators: usize, - chunks: Vec<&[u8]>, -) -> T { - let threshold = systematic_threshold(n_validators); - let shard_len = chunks.iter().next().unwrap().len(); - let mut systematic_bytes = Vec::with_capacity(shard_len * threshold); - - for i in (0..shard_len).step_by(2) { - for chunk in chunks.iter().take(threshold) { - systematic_bytes.push(chunk[i]); - systematic_bytes.push(chunk[i + 1]); - } - } - - Decode::decode(&mut &systematic_bytes[..]).unwrap() -} - -fn systematic_threshold(n_validators: usize) -> usize { - let mut threshold = (n_validators - 1) / 3; - if !is_power_of_two(threshold) { - threshold = next_lower_power_of_2(threshold); - } - - threshold -} -``` +You can find the concatenation procedure of systematic chunks for polkadot's erasure coding algorithm +[here](https://github.com/paritytech/reed-solomon-novelpoly/blob/be3751093e60adc20c19967f5443158552829011/reed-solomon-novelpoly/src/novel_poly_basis/mod.rs#L247) In a nutshell, it performs a column-wise concatenation with 2-byte chunks. The output could be zero-padded at the end, so scale decoding must be aware of the expected length in bytes and ignore -trailing zeros. +trailing zeros (this assertion is already being made for regular reconstruction). ### Availability recovery at present @@ -115,18 +86,15 @@ chunks. #### Properties -The function that decides the chunk index for a validator should be parameterized by at least -`(validator_index, block_number, core_index)` +The function that decides the chunk index for a validator will be parameterized by at least +`(validator_index, core_index)` and have the following properties: 1. deterministic 1. relatively quick to compute and resource-efficient. -1. when considering the other params besides `validator_index` as fixed, the function should describe a permutation -of the chunk indices -1. considering `block_number` as a fixed argument, the validators that map to the first N/3 chunk indices should -have as little overlap as possible for different paras scheduled on that relay parent. +1. when considering a fixed `core_index`, the function should describe a permutation of the chunk indices +1. the validators that map to the first N/3 chunk indices should have as little overlap as possible for different cores. -In other words, we want a uniformly distributed, deterministic mapping from `ValidatorIndex` to `ChunkIndex` per block -per core. +In other words, we want a uniformly distributed, deterministic mapping from `ValidatorIndex` to `ChunkIndex` per core. It's desirable to not embed this function in the runtime, for performance and complexity reasons. However, this means that the function needs to be kept very simple and with minimal or no external dependencies. @@ -145,7 +113,7 @@ pub fn get_chunk_index( core_index: CoreIndex ) -> ChunkIndex { let threshold = systematic_threshold(n_validators); // Roughly n_validators/3 - let core_start_pos = abs(core_index - block_number) * threshold; + let core_start_pos = core_index * threshold; (core_start_pos + validator_index) % n_validators } @@ -198,11 +166,12 @@ pub struct ChunkResponse { ``` An important thing to note is that in version 1, the `ValidatorIndex` value is always equal to the `ChunkIndex`. -Until the feature is enabled, this will also be true for version 2. However, after the feature is enabled, -this will generally not be true. +Until the chunk rotation feature is enabled, this will also be true for version 2. However, after the feature is +enabled, this will generally not be true. The requester will send the request to validator with index `V`. The responder will map the `V` validator index to the -`C` chunk index and respond with the `C`-th chunk. +`C` chunk index and respond with the `C`-th chunk. This mapping can be seamless, by having each validator store their +chunk by `ValidatorIndex` (just as before). The protocol implementation MAY check the returned `ChunkIndex` against the expected mapping to ensure that it received the right chunk. @@ -212,10 +181,10 @@ for easy access to the validator->chunk mapping. See [Appendix A](#appendix-a) f In any case, the requester MUST verify the chunk's proof using the provided index. -During availability-recovery, given that the requester may not know (if the mapping is not available) whether the received chunk corresponds to -the requested validator index, it has to keep track of received chunk indices and ignore duplicates. Such duplicates -should be considered the same as an invalid/garbage response (drop it and move on to the next validator - we can't -punish via reputation changes, because we don't know which validator misbehaved). +During availability-recovery, given that the requester may not know (if the mapping is not available) whether the +received chunk corresponds to the requested validator index, it has to keep track of received chunk indices and ignore +duplicates. Such duplicates should be considered the same as an invalid/garbage response (drop it and move on to the +next validator - we can't punish via reputation changes, because we don't know which validator misbehaved). ### Upgrade path @@ -235,10 +204,10 @@ don't support this mapping will not be able to participate in parachain consensu Additionally, an error will be logged when starting a validator with an older version, after the feature was enabled. -On the other hand, collators will not be required to upgrade in this step, as regular chunk recovery will work as before, -granted that version 1 of the networking protocol has been removed. Note that collators only perform -availability-recovery in rare, adversarial scenarios, so it is fine to not optimise for this case and let them upgrade -at their own pace. +On the other hand, collators will not be required to upgrade in this step (but are still require to upgrade for step 1), +as regular chunk recovery will work as before, granted that version 1 of the networking protocol has been removed. +Note that collators only perform availability-recovery in rare, adversarial scenarios, so it is fine to not optimise for +this case and let them upgrade at their own pace. To support enabling this feature via the runtime, we will use the `NodeFeatures` bitfield of the `HostConfiguration` struct (added in `https://github.com/paritytech/polkadot-sdk/pull/2177`). Adding and enabling a feature @@ -252,9 +221,9 @@ validator->chunk mapping ceases to be a 1:1 mapping and systematic recovery may very complicated (See [appendix A](#appendix-a)). This RFC assumes that availability-recovery processes initiated during disputes will only use regular recovery, as before. This is acceptable since disputes are rare occurrences in practice and is something that can be optimised later, if need be. Adding the `core_index` to the `CandidateReceipt` would -mitigate this problem and will likely be needed in the future for CoreJam. -[Related discussion about `CandidateReceipt`](https://forum.polkadot.network/t/pre-rfc-discussion-candidate-receipt-format-v2/3738) -- It's a breaking change that requires all validators and collators to upgrade their node version. +mitigate this problem and will likely be needed in the future for CoreJam and/or Elastic scaling. +[Related discussion about updating `CandidateReceipt`](https://forum.polkadot.network/t/pre-rfc-discussion-candidate-receipt-format-v2/3738) +- It's a breaking change that requires all validators and collators to upgrade their node version at least once. ## Testing, Security, and Privacy @@ -279,7 +248,7 @@ Not applicable. ### Compatibility This is a breaking change. See [upgrade path](#upgrade-path) section above. -All validators need to have upgraded their node versions before the feature will be enabled via a runtime upgrade +All validators and collators need to have upgraded their node versions before the feature will be enabled via a governance call. ## Prior Art and References @@ -289,7 +258,7 @@ See comments on the [tracking issue](https://github.com/paritytech/polkadot-sdk/ ## Unresolved Questions -- Is there a better upgrade path that would preserve backwards compatibility? +Not applicable. ## Future Directions and Related Material @@ -335,3 +304,6 @@ the reported core_index was indeed the one occupied by the candidate at the resp Another attempt could be to include in the message the relay block hash where the candidate was included. This information would be used in order to query the runtime API and retrieve the core index that the candidate was occupying. However, considering it's part of an unimported fork, the validator cannot call a runtime API on that block. + +Adding the `core_index` to the `CandidateReceipt` would solve this problem and would enable systematic recovery for all +dispute scenarios. From 4ae75296bfdeb1b2ca1e9d20f78c1a783475de47 Mon Sep 17 00:00:00 2001 From: alindima Date: Thu, 11 Jan 2024 14:50:36 +0200 Subject: [PATCH 11/11] fix --- text/0047-assignment-of-availability-chunks.md | 1 - 1 file changed, 1 deletion(-) diff --git a/text/0047-assignment-of-availability-chunks.md b/text/0047-assignment-of-availability-chunks.md index 2e4142058..477a66656 100644 --- a/text/0047-assignment-of-availability-chunks.md +++ b/text/0047-assignment-of-availability-chunks.md @@ -109,7 +109,6 @@ Pseudocode: pub fn get_chunk_index( n_validators: u32, validator_index: ValidatorIndex, - block_number: BlockNumber, core_index: CoreIndex ) -> ChunkIndex { let threshold = systematic_threshold(n_validators); // Roughly n_validators/3