split into separate adrs for protocol and chain versions

This commit is contained in:
Ethan Buchman 2018-07-28 13:03:03 -04:00
parent 9fc5b78ade
commit 67ef4ae0e1
3 changed files with 338 additions and 247 deletions

View File

@ -0,0 +1,100 @@
# Chain Versions
## TODO
- clarify how to handle slashing when ChainID changes
## Changelog
- 28-07-2018: Updates from review
- split into two ADRs - one for protocol, one for chains
- 16-07-2018: Initial draft - was originally joint ADR for protocol and chain
versions
## Context
Software and Protocol versions are covered in a separate ADR.
Here we focus on chain versions.
## Requirements
We need to version blockchains across protocols, networks, forks, etc.
We need chain identifiers and descriptions so we can talk about a multitude of chains,
and especially the differences between them, in a meaningful way.
### Networks
We need to support many independent networks running the same version of the software,
even possibly starting from the same initial state.
They must have distinct identifiers so that peers know which one they are joining and so
validators and users can prevent replay attacks.
Call this the `NetworkName` (note we currently call this `ChainID` in the software. In this
ADR, ChainID has a different meaning).
It represents both the application being run and the community or intention
of running it.
Peers only connect to other peers with the same NetworkName.
### Forks
We need to support existing networks upgrading and forking, wherein they may do any of:
- revert back to some height, continue with the same versions but new blocks
- arbitrarily mutate state at some height, continue with the same versions (eg. Dao Fork)
- change the AppVersion at some height
Note because of Tendermint's voting power threshold rules, a chain can only be extended under the "original" rules and under the new rules
if 1/3 or more is double signing, which is expressly prohibited, and is supposed to result in their punishment on both chains. Since they can censor
the punishment, the chain is expected to be hardforked to remove the validators. Thus, if both branches are to continue after a fork,
they will each require a new identifier, and the old chain identifier will be retired (ie. only useful for syncing history, not for new blocks)..
TODO: explain how to handle slashing when chain id changed!
We need a consistent way to describe forks.
## Proposal
### ChainDescription
ChainDescription is a complete immutable description of a blockchain. It takes the following form:
```
ChainDescription = <NetworkName>/<BlockVersion>/<AppVersion>/<StateHash>/<ValHash>/<ConsensusParamsHash>
```
Here, StateHash is the merkle root of the initial state, ValHash is the merkle root of the initial Tendermint validator set,
and ConsensusParamsHash is the merkle root of the initial Tendermint consensus parameters.
The `genesis.json` file must contain enough information to compute this value. It need not contain the StateHash or ValHash itself,
but contain the state from which they can be computed with the given protocol versions.
NOTE: consider splitting NetworkName into NetworkName and AppName - this allows
folks to independently use the same application for different networks (ie we
could imagine multiple communities of validators wanting to put up a Hub using
the same app but having a distinct network name. Arguably not needed if
differences will come via different initial state / validators).
#### ChainID
Define `ChainID = TMHASH(ChainDescriptor)`. It's the unique ID of a blockchain.
It should be Bech32 encoded when handled by users, eg. with `cosmoschain` prefix.
#### Forks and Uprades
When a chain forks or upgrades but continues the same history, it takes a new ChainDescription as follows:
```
ChainDescription = <ChainID>/x/<Height>/<ForkDescription>
```
Where
- ChainID is the ChainID from the previous ChainDescription (ie. its hash)
- `x` denotes that a change occured
- `Height` is the height the change occured
- ForkDescription has the same form as ChainDescription but for the fork
- this allows forks to specify new versions for tendermint or the app, as well as arbitrary changes to the state or validator set

View File

@ -0,0 +1,238 @@
# Protocol Versions
## TODO
- How do we want App to signal to Tendermint to update AppVersion? EndBlock tags
or new ProposeTx msg ?
- How to handle peers syncing from old heights across AppVersion changes
- How to / should we version the authenticated encryption handshake itself (ie.
upfront protocol negotiation for the P2PVersion)
## Changelog
- 28-07-2018: Updates from review
- split into two ADRs - one for protocol, one for chains
- include signalling for upgrades in header
- 16-07-2018: Initial draft - was originally joint ADR for protocol and chain
versions
## Context
The Software Version is covered by SemVer and described elsewhere.
It is not relevant to the protocol description, suffice to say that if any protocol version
changes, the software version changes, but not necessarily vice versa.
Software version shoudl be included in NodeInfo for convenience/diagnostics.
We are also interested in versioning across different blockchains in a
meaningful way, for instance to differentiate branches of a contentious
hard-fork. We leave that for a later ADR.
Here we focus on protocol versions.
## Requirements
We need to version components of the blockchain that may be independently upgraded.
We need to do it in a way that is scalable and maintainable - we can't just litter
the code with conditionals.
We can consider the complete version of the protocol to contain the following sub-versions:
BlockVersion, P2PVersion, AppVersion. These versions reflect the major sub-components
of the software that are likely to evolve together, at different rates, and in different ways,
as described below.
The BlockVersion defines the core of the blockchain data structures and
should change infrequently.
The P2PVersion defines how peers connect and communicate with eachother - it's
not part of the blockchain data structures, but defines the protocols used to build the
blockchain. It may change gradually.
The AppVersion ensures we only connect to peers that will compute the same App
state root. Tendermint otherwise doesn't care about the AppVersion, but it helps
to make it a native field for observability sake.
### BlockVersion
- All tendermint hashed data-structures (headers, votes, txs, responses, etc.).
- Note the semantic meaning of a transaction may change according to the AppVersion,
but the way txs are merklized into the header is part of the BlockVersion
- It should be the least frequent/likely to change.
- Tendermint should be stabilizing - it's just Atomic Broadcast.
- We can start considering for Tendermint v2.0 in a year
- It's easy to determine the version of a block from its serialized form
### P2PVersion
- All p2p and reactor messaging (messages, detectable behaviour)
- Will change gradually as reactors evolve to improve performance and support new features
- eg proposed new message types BatchTx in the mempool and HasBlockPart in the consensus
- It's easy to determine the version of a peer from its first serialized message/s
- New versions must be compatible with at least one old version to allow gradual upgrades
### AppVersion
- The ABCI state machine (txs, begin/endblock behaviour, commit hashing)
- Behaviour and message types will change abruptly in the course of the life of a chain
- Need to minimize complexity of the code for supporting different AppVersions at different heights
- Ideally, each version of the software supports only a *single* AppVersion at one time
- this means we checkout different versions of the software at different heights instead of littering the code
with conditionals
- minimize the number of data migrations required across AppVersion (ie. most AppVersion should be able to read the same state from disk as previous AppVersion).
- NOTE: we still need to support peers syncing from old heights across AppVersion changes
- this is a non-trivial and difficult point (!)
## Ideal
Each component of the software is independently versioned in a modular way and its easy to mix and match and upgrade.
Good luck pal ;)
## Proposal
Each of BlockVersion, AppVersion, P2PVersion is a monotonically increasing int64.
To use these versions, we need to update the block Header, the p2p NodeInfo, and the ABCI.
### Header
Block Header should include a `Version` struct as its first field like:
```
type Version struct {
BlockVersion ProtocolVersion
ChainID string
AppVersion ProtocolVersion
}
type ProtocolVersion struct {
Current int64
Next int64
}
```
Note this effectively makes BlockVersion the first field in the block Header.
Since we have settled on a proto3 header, the ability to read the BlockVersion out of the serialized header is unanimous.
Using a Version struct gives us more flexibility to add fields without breaking
the header.
The ProtocolVersion struct lets block proposer's specify both the current
Block and App versions, as well as proposed next versions.
### NodeInfo
NodeInfo should include a Version struct as its first field like:
```
type Version struct {
P2PVersion int64
ChainID string
BlockVersion int64
AppVersion int64
SoftwareVersion string
}
```
Note this effectively makes P2PVersion the first field in the NodeInfo, so it
should be easy to read this out of the serialized header if need be to facilitate an upgrade.
The SoftwareVersion here should include the name of the software client and
it's SemVer version - this is for convenience only. The other versions and
ChainID will determine peer compatibility (described below).
### ABCI
RequestInfo should add support for protocol versions like:
```
message RequestInfo {
string software_version
int64 block_version
int64 p2p_version
}
```
Similarly, ResponseInfo should return the versions:
```
message ResponseInfo {
string data
string software_version
int64 app_version
int64 last_block_height
bytes last_block_app_hash
}
```
TODO: we need some way for the app to tell Tendermint it wants to signal for a
Next AppVersion. Options:
- use new ProposeTx ABCI msg for this
- use tags in ResponseEndBlock and incorporate into Proposal, eg.
- KVPair: `("next_app_version", <new AppVersion>)`
- Could also include a tag for the height the upgrade should happen at or by
### BlockVersion
BlockVersion is included in both the Header and the NodeInfo.
Only connect to peers with the same or higher BlockVersion.
Changing BlockVersion should happen quite infrequently and ideally only for extreme emergency.
Note Ethereum has not had to make an upgrade like this (everything has been at state machine level, AFAIK).
### P2PVersion
P2PVersion is not included in the block Header, just the NodeInfo.
P2PVersion is the first field in the NodeInfo. NodeInfo is also proto3 so this is easy to read out.
Each P2PVersion must be compatible with at least one previous version. For each P2PVersion, we keep a list of the previous
versions it is compatible with.
Note we need the peer/reactor protocols to take the versions of peers into account when sending messages:
- don't send messages they don't understand
- don't send messages they don't expect
Doing this will be specific to the upgrades being made.
Note we also include the list of reactor channels in the NodeInfo and already don't send messages for channels the peer doesn't understand.
If upgrades always use new channels, this simplifies the development cost of backwards compatibility.
Note NodeInfo is only exchanged after the authenticated encryption handshake to ensure that it's private.
Doing any version exchange before encrypting could be considered information leakage, though I'm not sure
how much that matters compared to being able to upgrade the protocol.
XXX: if needed, can we change the meaning of the first byte of the first message to encode a handshake version?
this is the first byte of a 32-byte ed25519 pubkey.
### AppVersion
AppVersion is also included in the block Header and the NodeInfo.
AppVersion essentially defines how the AppHash is computed. Since peers with
different AppVersion will likely compute different AppHash for blocks,
we only maintain connections to peers with the correct AppVersion for the height they're at.
That is, we only connect to current peers with the same AppVersion, and to old peers that have the right AppVersion for where they
are in the syncing process.
Note this requires all validators to upgrade their versions at exactly the required height.
### Tendermint Changes
Compatibility requirements at the peer layer have already been described.
It may be valuable to support an `/unsafe_stop?height=_` endpoint to tell Tendermint to shutdown at a given height.
This could be use by an external manager process that oversees upgrades by
checking out and installing new software versions and restarting the process. It
would subscribe to the relevant upgrade event tags and call `/unsafe_stop` at
the correct height (of course only after getting approval from its user!)

View File

@ -1,247 +0,0 @@
# Protocol and Chain Versions
The Software Version is covered by SemVer and described elsewhere.
It is not relevant to the protocol description, suffice to say that if any protocol version
changes, the software version changes, but not necessarily vice versa. Software version is included in NodeInfo for convenience/diagnostics.
Here we focus on protocol and chain versions.
## Requirements
We need to version blockchains across protocols, networks, forks, etc.
We need to do it in a way that is scalable and maintainable - we can't just litter
the code with conditionals.
We need chain identifiers and descriptions so we can talk about a multitude of chains,
and especially the differences between them, in a meaningful way.
Let's start with versions.
### Protocol Version
The complete version of the protocol contains the following sub-versions:
BlockVersion, P2PVersion, AppVersion. These versions reflect the major sub-components
of the software that are likely to evolve together, at different rates, and in different ways,
as described below.
Note the BlockVersion defines the core of the blockchain data structures and
should change infrequently.
The P2PVersion defines how peers connect and communicate with eachother - it's
not part of the blockchain data structures, but defines the protocols used to build the
blockchain. It may change gradually.
The AppVersion ensures we only connect to peers that will compute the same App
state root. Tendermint otherwise doesn't care about the AppVersion, but it helps
to make it a native field for observability sake.
#### BlockVersion
- All tendermint hashed data-structures (headers, votes, txs, responses, etc.).
- Note the semantic meaning of a transaction may change according to the AppVersion,
but the way txs are merklized into the header is part of the BlockVersion
- It should be the least frequent/likely to change.
- Tendermint should be stabilizing - it's just Atomic Broadcast.
- We can start considering for Tendermint v2.0 in a year
- For now, assume that BlockVersion will not need to be upgraded while preserving the chain history,
except in cases of critical emergency.
- To effectively change it, dump state and start a new blockchain with the new BlockVersion.
- It's easy to determine the version of a block from its serialized form
#### P2PVersion
- All peer messaging (messages, detectable behaviour)
- Will change gradually as reactors evolve to improve performance and support new features
- eg proposed new message types BatchTx in the mempool and HasBlockPart in the consensus
- It's easy to determine the version of a peer from its first serialized message/s
- New versions must be compatible with at least one old version to allow gradual upgrades
#### AppVersion
- The ABCI state machine (txs, begin/endblock behaviour, commit hashing)
- Behaviour and message types will change abruptly in the course of the life of a chain
- Need to minimize complexity of the code for supporting different AppVersions at different heights
- Ideally, each version of the software supports only a *single* AppVersion at one time
- this means we checkout different versions of the software at different heights instead of littering the code
with conditionals
- minimize the number of data migrations required across AppVersion (ie. most AppVersion should be able to read the same state from disk as previous AppVersion).
- NOTE: we still need to support peers syncing from old heights across AppVersion changes
### Networks
We need to support many independent networks running the same version of the software,
even possibly starting from the same initial state.
They must have distinct identifiers so that peers know which one they are joining and so
validators and users can prevent replay attacks.
Call this the `NetworkName` (note we currently call this `ChainID` in the software. In this
ADR, ChainID has a different meaning).
It represents both the application being run and the community or intention
of running it.
Peers only connect to other peers with the same NetworkName.
### Forks
We need to support existing networks upgrading and forking, wherein they may do any of:
- revert back to some height, continue with the same versions but new blocks
- arbitrarily mutate state at some height, continue with the same versions (eg. Dao Fork)
- change the AppVersion at some height
Note because of Tendermint's voting power threshold rules, a chain can only be extended under the "original" rules and under the new rules
if 1/3 or more is double signing, which is expressly prohibited, and is supposed to result in their punishment on both chains. Since they can censor
the punishment, the chain is expected to be hardforked to remove the validators. Thus, if both branches are to continue after a fork,
they will each require a new identifier, and the old chain identifier will be retired (ie. only useful for syncing history, not for new blocks)..
TODO: explain how to handle slashing when chain id changed!
We need a consistent way to describe forks.
## Ideal
Each component of the software is independently versioned in a modular way and its easy to mix and match and upgrade.
Good luck pal ;)
## Proposal
### Versions
Each of BlockVersion, AppVersion, P2PVersion is a monotonically increasing int64.
An alternative might be to try to use Amino - ie. include the version number in the registration name for some type,
for instance the BlockVersion in the header. However this would require the registered-name of the header to change even
if it wasn't the header type itself that changed, which could be confusing. Thus, versions here will be integers in Amino encoded
structus. That said, Amino's flexibility (ie. both in registration of new types and proto3 extensibility in structs) will make
actual upgrades smoother.
#### BlockVersion
BlockVersion is the first field in the block Header. Since we have settled on a proto3 header,
the ability to read the BlockVersion out of the serialized header is unanimous.
BlockVersion is included in the NodeInfo and we only connect to peers with the same or higher BlockVersion.
In the event that we need to upgrade the core protocols and don't want to dump the state,
we can condition execution on the block version and use different handlers / message types as needed.
In such a case we may need to support connecting to old versions as well.
This isn't nice, but should happen quite infrequently and ideally only for extreme emergency.
Note Ethereum has not had to make an upgrade like this (everything has been at state machine level, AFAIK).
#### P2PVersion
P2PVersion is not included in the block Header.
P2PVersion is the first field in the NodeInfo. NodeInfo is also proto3 so this is easy to read out.
Each P2PVersion must be compatible with at least one previous version. For each P2PVersion, we keep a list of the previous
versions it is compatible with.
Note we need the peer/reactor protocols to take the versions of peers into account when sending messages:
- don't send messages they don't understand
- don't send messages they don't expect
Doing this will be specific to the upgrades being made.
Note we also include the list of reactor channels in the NodeInfo and already don't send messages for channels the peer doesn't understand.
If upgrades always use new channels, this simplifies the development cost of backwards compatibility.
Note NodeInfo is only exchanged after the authenticated encryption handshake to ensure that it's private.
Doing any version exchange before encrypting could be considered information leakage, though I'm not sure
how much that matters compared to being able to upgrade the protocol.
XXX: if needed, can we change the meaning of the first byte of the first message to encode a handshake version?
this is the first byte of a 32-byte ed25519 pubkey.
#### AppVersion
AppVersion is also included in the block Header and the NodeInfo.
AppVersion essentially defines how the AppHash is computed. Since peers with
different AppVersion will likely compute different AppHash for blocks,
we only maintain connections to peers with the correct AppVersion for the height they're at.
That is, we only connect to current peers with the same AppVersion, and to old peers that have the right AppVersion for where they
are in the syncing process.
Note this requires all validators to upgrade their versions at exactly the required height.
Effectively doing so requires an external manager process that knows when the version should be updated and can co-ordinate shutting down,
upgrading and restarting at the correct height.
While this requires additional overhead in the form of a new manager process, it avoids the state machine logic getting
cluttered with conditionals to effect different rules at different heights.
More details on how this manager process works later below.
### ChainDescription
ChainDescription is a complete immutable description of a blockchain. It takes the following form:
```
ChainDescription = <NetworkName>/<BlockVersion>/<AppVersion>/<StateHash>/<ValHash>/<ConsensusParamsHash>
```
Here, StateHash is the merkle root of the initial state, ValHash is the merkle root of the initial Tendermint validator set,
and ConsensusParamsHash is the merkle root of the initial Tendermint consensus parameters.
The `genesis.json` file must contain enough information to compute this value. It need not contain the StateHash or ValHash itself,
but contain the state from which they can be computed with the given protocol versions.
NOTE: consider splitting NetworkName into NetworkName and AppName - this allows
folks to independently use the same application for different networks (ie we
could imagine multiple communities of validators wanting to put up a Hub using
the same app but having a distinct network name. Arguably not needed if
differences will come via different initial state / validators).
is speci
XXX: should we split
#### ChainID
Define `ChainID = TMHASH(ChainDescriptor)`. It's the unique ID of a blockchain.
It should be Bech32 encoded when handled by users, eg. with `cosmoschain` prefix.
#### Forks and Uprades
When a chain forks or upgrades but continues the same history, it takes a new ChainDescription as follows:
```
ChainDescription = <ChainID>/x/<Height>/<ForkDescription>
```
Where
- ChainID is the ChainID from the previous ChainDescription (ie. its hash)
- `x` denotes that a change occured
- `Height` is the height the change occured
- ForkDescription has the same form as ChainDescription but for the fork
- this allows forks to specify new versions for tendermint or the app, as well as arbitrary changes to the state or validator set
### ABCI Changes
On RequestInfo, Tendermint should send: TMSoftwareVersion, BlockVersion, AppVersion
On ResponseInfo, App should send: AppSoftwareVersion, AppVersion
For the App to signal an upgrade, it should use the following tags (KVPairs) in ResponseEndBlock:
- `("upgrade_chain_description", <new ChainDescription>)`
- `("upgrade_chain_height", <height to upgrade>)`
This provides a simple manner for applications to signal to external processes that they want to upgrade, and what height to do it at.
By using the ChainDescription, the app signals a summary of all changes expected to be made and when and exactly how the ChainDescription will change.
### Tendermint Changes
Compatibility requirements at the peer layer have already been described.
The only other change necessary is an `/unsafe_stop?height=_` endpoint to tell Tendermint to shutdown at a given height.
This is to enable it to be stopped at the height signalled by `upgrade_chain_height` and to then be restarted using the new ChainDescription (ie. new app).
Note if the ValidatorHash or ConsensusParamsHash were force-changed, Tendermint will require the new information.
### Process Manager
A simple process managers subscribes to the `upgrade_chain` events and calls `/unsafe_stop` for the given height.
Note before calling /unsafe_stop it must get approval from its user!