-
Notifications
You must be signed in to change notification settings - Fork 973
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] EIP-7594: PeerDAS protocol #3574
Changes from 16 commits
d6a37ec
93dddd1
504b4f9
696d443
2cc7c87
665e6fa
9553d54
a72ece8
65be5b0
55db861
4477cc6
56e6a98
edeef07
b2a4657
7aab577
170dae5
547460c
d23452d
87e9702
428c166
c47d5f3
91dbbb3
e7c0d5f
8150f76
bb33f90
1acb1ff
cebf78a
8728561
5535e6a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,262 @@ | ||||||||
# EIP-7594 -- Data Availability Sampling Core | ||||||||
|
||||||||
**Notice**: This document is a work-in-progress for researchers and implementers. | ||||||||
|
||||||||
## Table of contents | ||||||||
|
||||||||
<!-- TOC --> | ||||||||
<!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||||||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||||||||
|
||||||||
- [Custom types](#custom-types) | ||||||||
- [Configuration](#configuration) | ||||||||
- [Data size](#data-size) | ||||||||
- [Networking](#networking) | ||||||||
- [Custody setting](#custody-setting) | ||||||||
- [Containers](#containers) | ||||||||
- [`DataColumnSidecar`](#datacolumnsidecar) | ||||||||
- [Helper functions](#helper-functions) | ||||||||
- [`get_custody_columns`](#get_custody_columns) | ||||||||
- [`compute_extended_data`](#compute_extended_data) | ||||||||
- [`compute_extended_matrix`](#compute_extended_matrix) | ||||||||
- [`get_data_column_sidecars`](#get_data_column_sidecars) | ||||||||
- [Custody](#custody) | ||||||||
- [Custody requirement](#custody-requirement) | ||||||||
- [Public, deterministic selection](#public-deterministic-selection) | ||||||||
- [Peer discovery](#peer-discovery) | ||||||||
- [Extended data](#extended-data) | ||||||||
- [Column gossip](#column-gossip) | ||||||||
- [Parameters](#parameters) | ||||||||
- [Reconstruction and cross-seeding](#reconstruction-and-cross-seeding) | ||||||||
- [Peer sampling](#peer-sampling) | ||||||||
- [Peer scoring](#peer-scoring) | ||||||||
- [DAS providers](#das-providers) | ||||||||
- [A note on fork choice](#a-note-on-fork-choice) | ||||||||
- [FAQs](#faqs) | ||||||||
- [Row (blob) custody](#row-blob-custody) | ||||||||
- [Subnet stability](#subnet-stability) | ||||||||
|
||||||||
<!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||||||||
<!-- /TOC --> | ||||||||
|
||||||||
## Custom types | ||||||||
|
||||||||
We define the following Python custom types for type hinting and readability: | ||||||||
|
||||||||
| Name | SSZ equivalent | Description | | ||||||||
| - | - | - | | ||||||||
| `DataColumn` | `List[Cell, MAX_BLOBS_PER_BLOCK]` | The data of each column in EIP-7594 | | ||||||||
| `ExtendedMatrix` | `List[Cell, MAX_BLOBS_PER_BLOCK * NUMBER_OF_COLUMNS]` | The full data of one-dimensional erasure coding extended blobs (in row major format) | | ||||||||
| `FlatExtendedMatrix` | `List[BLSFieldElement, MAX_BLOBS_PER_BLOCK * FIELD_ELEMENTS_PER_BLOB * NUMBER_OF_COLUMNS]` | The flattened format of `ExtendedMatrix` | | ||||||||
hwwhww marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
## Configuration | ||||||||
|
||||||||
### Data size | ||||||||
|
||||||||
| Name | Value | Description | | ||||||||
| - | - | - | | ||||||||
| `NUMBER_OF_COLUMNS` | `uint64((FIELD_ELEMENTS_PER_BLOB * 2) // FIELD_ELEMENTS_PER_CELL)` (= 128) | Number of columns in the extended data matrix. | | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Low priority, feel free to merge without this. |
||||||||
|
||||||||
### Networking | ||||||||
|
||||||||
| Name | Value | Description | | ||||||||
| - | - | - | | ||||||||
| `DATA_COLUMN_SIDECAR_SUBNET_COUNT` | `32` | The number of data column sidecar subnets used in the gossipsub protocol | | ||||||||
|
||||||||
### Custody setting | ||||||||
|
||||||||
| Name | Value | Description | | ||||||||
| - | - | - | | ||||||||
| `SAMPLES_PER_SLOT` | `8` | Number of `DataColumn` random samples a node queries per slot | | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it make sense to define a config/preset value for the probability of the data being available/unavailable and derive There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes this should be related with probablity, however i would prefer to have this as the config/preset constant, probablity analysis vs sample size could be attached with the specs There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would nice to have a justification for the number on why this is optimal and no more/less is needed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If >50% of the columns are missing, the probability that We have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just brainstorming about resiliency without adding significant security risk, some alternatives came to mind:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It depends what kind of security you are concerned with. From the network's perspective, it makes no difference how you get your samples, it is only important that you do. Everyone could get them from a single source and it wouldn't make any difference. From an individual node's perspective, it does matter somewhat, because getting them from multiple nodes (assuming they are actually unrelated) would make you less susceptible to targeted release of data. Without getting into details too much, I don't think this is something to be particularly concerned about, and I don't think there is much of a reason to try hard to get each sample from a different peer.
Yes, there is. The columns you choose must be random. You should not treat the node ids of your peers as random. |
||||||||
| `CUSTODY_REQUIREMENT` | `1` | Minimum number of subnets an honest node custodies and serves samples from | | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed with @dankrad, I think this should be higher than 1, because it is the most important parameter in determining how strong are the pre-sampling guarantees we get, i.e., how hard it is for an unavailable block to get a majority of the votes and become canonical, even when employing the trailing fork-choice. Ideally, we would want to ensure that only a small fraction of the validators can be tricked into voting for an unavailable block. Imo, a suitable value would be >= 6. We might then also want to increase the number of subnets to 64, to keep the bandwidth consumption low. For reference, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that this is currently subnet custody which has many columns depending on the parametrization (4 currently if the parametrization is 128 columns across 32 subnets (128/32)) I find the way we are doing this parametrization a bit confusing or at least difficult to parse (as seen by the above misunderstanding) The name should at least say explicitly if it's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that the name should be For example, someone can make 49% of the subnets available (not making a subnet available here means not publishing any of the columns for that subnet), and 49% of validators will still succeed in downloading all columns they need. If on the other end the custody requirement had been 4 random columns, making 49% of columns available would only trick 1/16 of the validators. This is also the case if validators custody 4 subnets. Another way to think about it is: whatever unit we choose for custody ends up being the relevant sampling unit for the purpose of pre-peer-sampling security. If we choose subnet custody, we are doing subnet sampling during distribution and column sampling later. If we want subnet sampling to be meaningfully secure by itself, we need the subnet custody requirement to be high enough. |
||||||||
| `TARGET_NUMBER_OF_PEERS` | `70` | Suggested minimum peer count | | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
|
||||||||
### Containers | ||||||||
|
||||||||
#### `DataColumnSidecar` | ||||||||
|
||||||||
```python | ||||||||
class DataColumnSidecar(Container): | ||||||||
hwwhww marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
index: ColumnIndex # Index of column in extended matrix | ||||||||
column: DataColumn | ||||||||
kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] | ||||||||
kzg_proofs: List[KZGProof, MAX_BLOB_COMMITMENTS_PER_BLOCK] | ||||||||
signed_block_header: SignedBeaconBlockHeader | ||||||||
kzg_commitments_inclusion_proof: Vector[Bytes32, KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH] | ||||||||
``` | ||||||||
|
||||||||
### Helper functions | ||||||||
|
||||||||
#### `get_custody_columns` | ||||||||
|
||||||||
```python | ||||||||
def get_custody_columns(node_id: NodeID, custody_subnet_count: uint64) -> Sequence[ColumnIndex]: | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We currently have a function that maps node-id -> subnets and I think we plan to also do node-id -> sync-committees etc. The idea is we just have a single function that maps node-id -> shard. The benefit here is:
If others agree, I can draft a spec PR that implements this idea, perhaps it would be more explicit that way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there are 64 shards but we want to split the blob data in 32 parts, do we map each of the 32 parts to two shards? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep. The number of shards would be the maximum granularity of splitting nodes. Like we could have 128 shards, in case we wanted to divide the network into 128 and assign subnets etc. I think 64 makes sense at the moment tho, because we currently subscribe to two subnets per node, so 2 shards there also. However if we want to have 128 data columns and split this up equally, we might want to have 128 shards, then subnets would be mapped to 4 shards (or vice versa) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Can it be extended to consider
Would it separate the long-lived subscriptions and the mid-term-lived subscriptions? We may add a PeerDAS assignment period into the deterministic function.
I think it would be good to see a PR for it. One minor suggestion: I like the "Network Sharding" naming. However, it might be ambiguous with the data sharding we're mostly talking about. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For reference, the ethresearch post of network sharding is here https://ethresear.ch/t/network-shards-concept-idea/17417 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@AgeManning please feel free to ping me if you want my assistance or maybe want to delegate that to me There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
yep that should be easy to do (If I understand your question).
This is a good point I'd need to think about. The period of rotation might need to be made a parameter of the function. This would mean that nodes will be on different shards based on this parameter. I guess we shouldn't force rotation periods for different use-cases to be the same.
Thanks. I have been traveling and have a conference next week, but I will endeavor to get a PR up for this that we can work from. Feel free to make one earlier if you have the time tho. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm in support of moving in this direction. Open a PR to this one. We'll likely get this merged in the meantime and can review the mapping proposal in isolation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apologies for the delay. Here is the proposal: #3623 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Great! Thanks @AgeManning If this PR is adopted then I'd suggest to make The above would allow push sampling approach: a sampling node could be connected to all (or a subset) of network shards (without subscribing to any column subnets). Some short period (sub-slot) prior to sampling a node subscribes to selected subnets and immediately unsubscribes upon sample receiving. This push approach would allow to
|
||||||||
assert custody_subnet_count <= DATA_COLUMN_SIDECAR_SUBNET_COUNT | ||||||||
|
||||||||
subnet_ids = [] | ||||||||
i = 0 | ||||||||
while len(subnet_ids) < custody_subnet_count: | ||||||||
subnet_id = ( | ||||||||
bytes_to_uint64(hash(uint_to_bytes(uint64(node_id + i)))[0:8]) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just sanity checking that this 256-bit to 64-bit casting is well-defined in the spec (or at least done somewhere else) My initial check shows that we are just using the ssz-typing lib here which will be opinionated but not in a way that is actually defined in the SSZ spec (no mention of casting as it's a serialization spec) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why cast this down to 64-bit? why not just pass in the 256-bit value to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actually we're doing it under the hood in places with bytes_to_uint64 so I guess it's well defined There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think it's because you need to do the However, I think it should be |
||||||||
% DATA_COLUMN_SIDECAR_SUBNET_COUNT | ||||||||
) | ||||||||
if subnet_id not in subnet_ids: | ||||||||
subnet_ids.append(subnet_id) | ||||||||
i += 1 | ||||||||
assert len(subnet_ids) == len(set(subnet_ids)) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like it shouldn't be here. It's an assertion we should have in our tests, but that it's not a possible condition here given the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree with this. This assertion is always false. The intention is only to catch a bug. |
||||||||
|
||||||||
columns_per_subnet = NUMBER_OF_COLUMNS // DATA_COLUMN_SIDECAR_SUBNET_COUNT | ||||||||
return [ | ||||||||
ColumnIndex(DATA_COLUMN_SIDECAR_SUBNET_COUNT * i + subnet_id) | ||||||||
for i in range(columns_per_subnet) | ||||||||
for subnet_id in subnet_ids | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a bit pythonic, the double list comprehension Some of the difficulty to parse is in relation to the double list comprehension, but I think more of it comes from the weird column vs subnet custody |
||||||||
] | ||||||||
``` | ||||||||
|
||||||||
#### `compute_extended_data` | ||||||||
|
||||||||
```python | ||||||||
def compute_extended_data(data: Sequence[BLSFieldElement]) -> Sequence[BLSFieldElement]: | ||||||||
# TODO | ||||||||
# pylint: disable=unused-argument | ||||||||
... | ||||||||
``` | ||||||||
|
||||||||
#### `compute_extended_matrix` | ||||||||
|
||||||||
```python | ||||||||
def compute_extended_matrix(blobs: Sequence[Blob]) -> FlatExtendedMatrix: | ||||||||
matrix = [compute_extended_data(blob) for blob in blobs] | ||||||||
return FlatExtendedMatrix(matrix) | ||||||||
``` | ||||||||
|
||||||||
#### `get_data_column_sidecars` | ||||||||
|
||||||||
```python | ||||||||
def get_data_column_sidecars(signed_block: SignedBeaconBlock, | ||||||||
blobs: Sequence[Blob]) -> Sequence[DataColumnSidecar]: | ||||||||
signed_block_header = compute_signed_block_header(signed_block) | ||||||||
block = signed_block.message | ||||||||
kzg_commitments_inclusion_proof = compute_merkle_proof( | ||||||||
block.body, | ||||||||
get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments'), | ||||||||
) | ||||||||
cells_and_proofs = [compute_cells_and_proofs(blob) for blob in blobs] | ||||||||
blob_count = len(blobs) | ||||||||
cells = [cells_and_proofs[i][0] for i in range(blob_count)] | ||||||||
proofs = [cells_and_proofs[i][1] for i in range(blob_count)] | ||||||||
sidecars = [] | ||||||||
for column_index in range(NUMBER_OF_COLUMNS): | ||||||||
column = DataColumn([cells[row_index][column_index] | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we usually use this syntax and general prefer: var = Stuff([
value[i]
for i in list
]) |
||||||||
for row_index in range(blob_count)]) | ||||||||
kzg_proof_of_column = [proofs[row_index][column_index] | ||||||||
for row_index in range(blob_count)] | ||||||||
sidecars.append(DataColumnSidecar( | ||||||||
index=column_index, | ||||||||
column=column, | ||||||||
kzg_commitments=block.body.blob_kzg_commitments, | ||||||||
kzg_proofs=kzg_proof_of_column, | ||||||||
signed_block_header=signed_block_header, | ||||||||
kzg_commitments_inclusion_proof=kzg_commitments_inclusion_proof, | ||||||||
)) | ||||||||
return sidecars | ||||||||
``` | ||||||||
|
||||||||
## Custody | ||||||||
|
||||||||
### Custody requirement | ||||||||
|
||||||||
Each node downloads and custodies a minimum of `CUSTODY_REQUIREMENT` subnets per slot. The particular columns that the node is required to custody are selected pseudo-randomly (more on this below). | ||||||||
hwwhww marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
A node *may* choose to custody and serve more than the minimum honesty requirement. Such a node explicitly advertises a number greater than `CUSTODY_REQUIREMENT` via the peer discovery mechanism -- for example, in their ENR (e.g. `custody_lines: 4` if the node custodies `4` subnets each slot) -- up to a `DATA_COLUMN_SIDECAR_SUBNET_COUNT` (i.e. a super-full node). | ||||||||
hwwhww marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
A node stores the custodied columns for the duration of the pruning period and responds to peer requests for samples on those columns. | ||||||||
|
||||||||
### Public, deterministic selection | ||||||||
|
||||||||
The particular columns that a node custodies are selected pseudo-randomly as a function (`get_custody_columns`) of the node-id and custody size -- importantly this function can be run by any party as the inputs are all public. | ||||||||
|
||||||||
*Note*: increasing the `custody_size` parameter for a given `node_id` extends the returned list (rather than being an entirely new shuffle) such that if `custody_size` is unknown, the default `CUSTODY_REQUIREMENT` will be correct for a subset of the node's custody. | ||||||||
|
||||||||
## Peer discovery | ||||||||
|
||||||||
At each slot, a node needs to be able to readily sample from *any* set of columns. To this end, a node should find and maintain a set of diverse and reliable peers that can regularly satisfy their sampling demands. | ||||||||
hwwhww marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
A node runs a background peer discovery process, maintaining at least `TARGET_NUMBER_OF_PEERS` of various custody distributions (both `custody_size` and column assignments). The combination of advertised `custody_size` size and public node-id make this readily and publicly accessible. | ||||||||
|
||||||||
`TARGET_NUMBER_OF_PEERS` should be tuned upward in the event of failed sampling. | ||||||||
|
||||||||
*Note*: while high-capacity and super-full nodes are high value with respect to satisfying sampling requirements, a node should maintain a distribution across node capacities as to not centralize the p2p graph too much (in the extreme becomes hub/spoke) and to distribute sampling load better across all nodes. | ||||||||
|
||||||||
*Note*: A DHT-based peer discovery mechanism is expected to be utilized in the above. The beacon-chain network currently utilizes discv5 in a similar method as described for finding peers of particular distributions of attestation subnets. Additional peer discovery methods are valuable to integrate (e.g., latent peer discovery via libp2p gossipsub) to add a defense in breadth against one of the discovery methods being attacked. | ||||||||
|
||||||||
## Extended data | ||||||||
|
||||||||
In this construction, we extend the blobs using a one-dimensional erasure coding extension. The matrix comprises maximum `MAX_BLOBS_PER_BLOCK` rows and fixed `NUMBER_OF_COLUMNS` columns, with each row containing a `Blob` and its corresponding extension. | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It would be nice if there were a defined type for this, such as
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How often will There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I imagine |
||||||||
|
||||||||
## Column gossip | ||||||||
|
||||||||
### Parameters | ||||||||
|
||||||||
For each column -- use `data_column_sidecar_{subnet_id}` subnets, where each column index maps to the `subnet_id`. The sidecars can be computed with `get_data_column_sidecars(signed_block: SignedBeaconBlock, blobs: Sequence[Blob])` helper. | ||||||||
hwwhww marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
To custody a particular column, a node joins the respective gossip subnet. Verifiable samples from their respective column are gossiped on the assigned subnet. | ||||||||
|
||||||||
### Reconstruction and cross-seeding | ||||||||
|
||||||||
If the node obtains 50%+ of all the columns, they can reconstruct the full data matrix via `recover_samples_impl` helper. | ||||||||
|
||||||||
If a node fails to sample a peer or fails to get a column on the column subnet, a node can utilize the Req/Resp message to query the missing column from other peers. | ||||||||
|
||||||||
Once the node obtain the column, the node should send the missing columns to the column subnets. | ||||||||
hwwhww marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
*Note*: A node always maintains a matrix view of the rows and columns they are following, able to cross-reference and cross-seed in either direction. | ||||||||
|
||||||||
*Note*: There are timing considerations to analyze -- at what point does a node consider samples missing and choose to reconstruct and cross-seed. | ||||||||
|
||||||||
*Note*: There may be anti-DoS and quality-of-service considerations around how to send samples and consider samples -- is each individual sample a message or are they sent in aggregate forms. | ||||||||
|
||||||||
## Peer sampling | ||||||||
|
||||||||
At each slot, a node makes (locally randomly determined) `SAMPLES_PER_SLOT` queries for samples from their peers via `DataColumnSidecarByRoot` request. A node utilizes `get_custody_columns` helper to determine which peer(s) to request from. If a node has enough good/honest peers across all rows and columns, this has a high chance of success. | ||||||||
|
||||||||
## Peer scoring | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Along with poor/no response of requested samples, if a peer isn't subscribed to the required computed column subnets they should be disconnected and banned |
||||||||
|
||||||||
Due to the deterministic custody functions, a node knows exactly what a peer should be able to respond to. In the event that a peer does not respond to samples of their custodied rows/columns, a node may downscore or disconnect from a peer. | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be way better implementation wise if the spec defines a particular time within the slot when we should do the sampling.
which would complicate peer scoring considerably implementation wise. If the trailing fork choice filter described below is viable security wise, then maybe we only sample at the 4 second mark where validators have to attest to slot N There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think since it's for the past slot, so it should defintely be completed by before you import the block, one could try doing it in the last 3 seconds of previous block? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think, timing is TBD. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Imo it would make sense to start sampling as soon as possible, e.g. as soon as you receive either the block or a sidecar (which has all of the commitments). I am not sure how this affect peer scoring. Perhaps a node could downscore a peer only if they fail to provide a sample after some deadline, and if the node has good certainty that this sample should be available? E.g. for samples for slot n, you could wait until you receive attestations from slot n, and only downscore peers if the block is canonical |
||||||||
|
||||||||
## DAS providers | ||||||||
|
||||||||
A DAS provider is a consistently-available-for-DAS-queries, super-full (or high capacity) node. To the p2p, these look just like other nodes but with high advertised capacity, and they should generally be able to be latently found via normal discovery. | ||||||||
|
||||||||
DAS providers can also be found out-of-band and configured into a node to connect to directly and prioritize. Nodes can add some set of these to their local configuration for persistent connection to bolster their DAS quality of service. | ||||||||
|
||||||||
Such direct peering utilizes a feature supported out of the box today on all nodes and can complement (and reduce attackability and increase quality-of-service) alternative peer discovery mechanisms. | ||||||||
|
||||||||
## A note on fork choice | ||||||||
|
||||||||
*Fork choice spec TBD, but it will just be a replacement of `is_data_available()` call in Deneb with column sampling instead of full download. Note the `is_data_available(slot_N)` will likely do a `-1` follow distance so that you just need to check the availability of slot `N-1` for slot `N` (starting with the block proposer of `N`).* | ||||||||
hwwhww marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there more information on how the trailing slot fork choice works during network partitions or longer forks ? During any event where you might temporarily have your peers with split views(hard fork, consensus bug), the peers that you are connected to for the particular column subnets will neither have the block or the data. So when sampling for How does sampling work when the network is undergoing stress and possible churn ? ( constant peer disconnections and new connections to find peers with the desired column subnets) . Is there a mode where we can fall back to so that a block and its data which was correctly broadcasted and propagated is marked as available rather than unavailable during such a situation. |
||||||||
|
||||||||
The fork choice rule (essentially a DA filter) is *orthogonal to a given DAS design*, other than the efficiency of a particular design impacting it. | ||||||||
|
||||||||
In any DAS design, there are probably a few degrees of freedom around timing, acceptability of short-term re-orgs, etc. | ||||||||
|
||||||||
For example, the fork choice rule might require validators to do successful DAS on slot N to be able to include block of slot `N` in its fork choice. That's the tightest DA filter. But trailing filters are also probably acceptable, knowing that there might be some failures/short re-orgs but that they don't hurt the aggregate security. For example, the rule could be — DAS must be completed for slot N-1 for a child block in N to be included in the fork choice. | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the example is correct for DAS in the same slot? (tightest DA filter) |
||||||||
|
||||||||
Such trailing techniques and their analysis will be valuable for any DAS construction. The question is — can you relax how quickly you need to do DA and in the worst case not confirm unavailable data via attestations/finality, and what impact does it have on short-term re-orgs and fast confirmation rules. | ||||||||
|
||||||||
## FAQs | ||||||||
|
||||||||
### Row (blob) custody | ||||||||
|
||||||||
In the one-dimension construction, a node samples the peers by requesting the whole `DataColumn`. In reconstruction, a node can reconstruct all the blobs by 50% of the columns. Note that nodes can still download the row via `blob_sidecar_{subnet_id}` subnets. | ||||||||
hwwhww marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
The potential benefits of having row custody could include: | ||||||||
|
||||||||
1. Allow for more "natural" distribution of data to consumers -- e.g., roll-ups -- but honestly, they won't know a priori which row their blob is going to be included in in the block, so they would either need to listen to all rows or download a particular row after seeing the block. The former looks just like listening to column [0, N) and the latter is req/resp instead of gossiping. | ||||||||
hwwhww marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
2. Help with some sort of distributed reconstruction. Those with full rows can compute extensions and seed missing samples to the network. This would either need to be able to send individual points on the gossip or would need some sort of req/resp faculty, potentially similar to an `IHAVEPOINTBITFIELD` and `IWANTSAMPLE`. | ||||||||
|
||||||||
However, for simplicity, we don't assign row custody assignments to nodes in the current design. | ||||||||
|
||||||||
### Subnet stability | ||||||||
|
||||||||
To start with a simple, stable backbone, for now, we don't shuffle the subnet assignments via the deterministic custody selection helper `get_custody_columns`. However, staggered rotation likely needs to happen on the order of the pruning period to ensure subnets can be utilized for recovery. For example, introducing an `epoch` argument allows the function to maintain stability over many epochs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For initial parametrization, I'd default to these values being equal. I'd only deviate from that
len(columns) == len(subnets)
if simulation/analysis shows we need to to support whatever we are targeting.