Skip to content
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

Initial spec for upgradability #64

Merged
merged 6 commits into from
Jun 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions specs/ChainSpec/README.md

This file was deleted.

100 changes: 100 additions & 0 deletions specs/ChainSpec/Upgradability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Upgradability

This part of specification describes specifics of upgrading the protocol, and touches on few different parts of the system.

Three different levels of upgradability are:
1. Updating without any changes to underlaying data structures or protocol;
2. Updating when underlaying data structures changed (config, database or something else internal to the node and probably client specific);
ilblackdragon marked this conversation as resolved.
Show resolved Hide resolved
3. Updating with protocol changes that all validating nodes must adjust to.

## Versioning

There are 2 different important versions:
- Version of binary defines it's internal data structures / database and configs. This version is client specific and doesn't need to be matching between nodes.
- Version of the protocol, defining the "language" nodes are speaking.

```rust
/// Latest version of protocol that this binary can work with.
type ProtocolVersion = u32;
```

## Client versioning
ilblackdragon marked this conversation as resolved.
Show resolved Hide resolved

Clients should follow [semantic versioning](https://semver.org/).
Specifically:
- MAJOR version defines protocol releases.
ilblackdragon marked this conversation as resolved.
Show resolved Hide resolved
- MINOR version defines changes that are client specific but require database migration, change of config or something similar. This includes client-specific features. Client should execute migrations on start, by detecting that information on disk is produced by previous version and auto-migrate it to new one.
- PATCH version defines when bug fixes, which should not require migrations or protocol changes.

Clients can define how current version of data is stored and migrations applied.
General recommendation is to store version in the database and on binary start, check version of database and perform required migrations.

## Protocol Upgrade

Generally, we handle data structure upgradability via enum wrapper around it. See `BlockHeader` structure for example.

### Versioned data structures

Given we expect many data structures to change or get updated as protocol evolves, a few changes are required to support that.

The major one is adding backward compatible `Versioned` data structures like this one:

```rust
enum VersionedBlockHeader {
BlockHeaderV1(BlockHeaderV1),
/// Current version, where `BlockHeader` is used internally for all operations.
BlockHeaderV2(BlockHeader),
}
```

Where `VersionedBlockHeader` will be stored on disk and sent over the wire.
This allows to encode and decode old versions (up to 256 given https://borsh.io specficiation). If some data structures has more than 256 versions, old versions are probably can be retired and reused.

Internally current version is used. Previous versions either much interfaces / traits that are defined by different components or are up-casted into the next version (saving for hash validation).

### Consensus

| Name | Value |
| - | - |
| `PROTOCOL_UPGRADE_BLOCK_THRESHOLD` | `80%` |
| `PROTOCOL_UPGRADE_NUM_EPOCHS` | `2` |

The way the version will be indicated by validators, will be via

```rust
/// Add `version` into block header.
struct BlockHeaderInnerRest {
...
/// Latest version that current producing node binary is running on.
version: ProtocolVersion,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just double checking for my understanding. This field represents the highest version number the node producing the block could support, but not the version of the block header itself, correct? The actual version of the block header would be determined based on the enum variant it deserializes to.

If that is the case I wonder if a different name should be used. Say proposed_version or something like that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct, I used latest_protocol_version in implementation here - near/nearcore#2701

}
```

The condition to switch to next protocol version is based on % of stake `PROTOCOL_UPGRADE_NUM_EPOCHS` epochs prior indicated about switching to the next version:

```python
def next_epoch_protocol_version(last_block):
"""Determines next epoch's protocol version given last block."""
epoch_info = epoch_manager.get_epoch_info(last_block)
# Find epoch that decides if version should change by walking back.
for _ in PROTOCOL_UPGRADE_NUM_EPOCHS:
epoch_info = epoch_manager.prev_epoch(epoch_info)
# Stop if this is the first epoch.
if epoch_info.prev_epoch_id == GENESIS_EPOCH_ID:
break
versions = collections.defaultdict(0)
# Iterate over all blocks in previous epoch and collect latest version for each validator.
authors = {}
for block in epoch_info:
author_id = epoch_manager.get_block_producer(block.header.height)
if author_id not in authors:
authors[author_id] = block.header.rest.version
# Weight versions with stake of each validator.
for author in authors:
versions[authors[author] += epoch_manager.validators[author].stake
(version, stake) = max(versions.items(), key=lambda x: x[1])
if stake > PROTOCOL_UPGRADE_BLOCK_THRESHOLD * epoch_info.total_block_producer_stake:
return version
# Otherwise return version that was used in that deciding epoch.
return epoch_info.version
ilblackdragon marked this conversation as resolved.
Show resolved Hide resolved
```
3 changes: 2 additions & 1 deletion specs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
- [Access Key](DataStructures/AccessKey.md)
- [Transaction](DataStructures/Transaction.md)
- [Architecture](Architecture.md)
- [Chain specification](ChainSpec/README.md)
- Chain specification
- [Consensus](ChainSpec/Consensus.md)
- [Upgradability](ChainSpec/Upgradability.md)
- [Transactions](ChainSpec/Transactions.md)
- [Runtime specification](RuntimeSpec/README.md)
- [FunctionCall](RuntimeSpec/FunctionCall.md)
Expand Down