Skip to content

Phase 0 to 1 upgradability: fork/version boundary deserialization issue #1475

@hwwhww

Description

@hwwhww

Thanks to @dankrad for giving input of the proposed solution.

tldr: (i) The SSZ object versioning is based on protocol versions, and (ii) heterogeneous list/vector is not allowed in SSZ. So with the current phase 1 spec (#1427), when we cross the version boundary, we are not able to process the previous operations with the new scheme.

Issue

Versioning in the networking layer

  • For the individual operation gossip topics messages (e.g., attestation, voluntary_exit) are versioned by protocol IDs. So it's fine to receive messages of different versions at the single-operation level.
  • The Protocol ID would map to the specific schema version of the SSZ object definition, so that we can deserialize it with the corresponding schema. So 👍

Fork/version boundary operations

  • However, when crossing the version boundary, the current BeaconBlockBody.[operations]: List[Operation, N] only hold for one version of the given Operation.
  • The block proposer cannot include both the previous version and current version objects in the same SSZ List. For example:

Example 1: Attestation inclusion

  • In the phase 1 protocol changes, we can forecast that at least AttestationData fields will be changed.
  • For the attesters of the last slot of the previous version, if they create attestation with the old schema, their attestation will not be able to be included in the block of the first slot of the current version.
  • If the attesters create attestation with the new schema, it's possible to be included in the next block. But, should they?
  • Moreover, how about the late attestations?

Example 2: Slashing operations

  • AttesterSlashing contains attestation_1: IndexedAttestation and attestation_2: IndexedAttestation, so similar to Attestation, the new version block can't include heterogeneous AttesterSlashing formats. So we can't slash the malicious attesters at the previous version epoch.
  • ProposerSlashing contains header_1: BeaconBlockHeader and header_2: BeaconBlockHeader, which are the conflicting beacon block headers of the slot of the proposer to slash. If we change BeaconBlockHeader format in the future version, we can't slash the malicious proposer that double-proposed at the previous version epoch.

Example 3: BeaconState.previous_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH].

  • At the first epoch of the new version, this field should be able to hold the previous version of PendingAttestation.

Proposed solution

  • Use SSZ Union type to make Phase1 Lists compatable with both previous & current version of objects. e.g., Phase1BeaconBlockBody.attestations: List[Union[Phase0Attestation, Phase1Attestation], N].
  • Pros:
    • The proposers can now include both versions of SSZ objects.
  • Cons:
    • Compare to rejecting all incompatible operations, it's more complex to handle both versions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions