Skip to content
This repository has been archived by the owner on Mar 24, 2023. It is now read-only.

Add state representation #32

Merged
merged 49 commits into from
Jun 16, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
a453c4d
Add basic state representation.
adlerjohn May 19, 2020
62420a9
Fix typo is size of validator voting power.
adlerjohn May 19, 2020
cf6e484
Update specs/data_structures.md
adlerjohn May 20, 2020
a1340ff
Add nonce field to accounts.
adlerjohn May 20, 2020
a2adee4
First draft refactor: validators and accounts in a single tree.
adlerjohn May 25, 2020
6201f4c
Add consensus constants for unbonding duration and maximum active val…
adlerjohn May 26, 2020
2b14d8d
Add validator and delegation structs to accounts.
adlerjohn May 26, 2020
9ce5d6c
Add additional validator and delegation fields.
adlerjohn May 27, 2020
06f6436
Add explanation for validator status.
adlerjohn Jun 1, 2020
05b2d47
Add explication for delegation status.
adlerjohn Jun 1, 2020
00d4675
Add slashing fields to validator.
adlerjohn Jun 1, 2020
5a60665
Clean up.
adlerjohn Jun 1, 2020
2df269d
Add accumulation of voting power and rewards to validators and delega…
adlerjohn Jun 2, 2020
4cf479a
Reduce the precision of voting power to whole coins (i.e. drop 9 zero…
adlerjohn Jun 2, 2020
411925c
Remove todo.
adlerjohn Jun 2, 2020
7f31112
Add rules for calculating rewards and penalties for delegations and v…
adlerjohn Jun 3, 2020
34d9ddc
Clean up.
adlerjohn Jun 3, 2020
8339f86
Add rule to update accumulated voting power also when validator begin…
adlerjohn Jun 3, 2020
61cbe55
Clarify that accumulated voting power is in whole coins.
adlerjohn Jun 3, 2020
f7bc92b
Add commission calculations.
adlerjohn Jun 3, 2020
0828bf3
Fix tables.
adlerjohn Jun 3, 2020
3f320e0
Fix commissions.
adlerjohn Jun 3, 2020
d87ef53
Rename calculating rewards and penalties to distributing.
adlerjohn Jun 6, 2020
08dfbc5
Clean up.
adlerjohn Jun 6, 2020
313388e
Migrate rationale for reward distribution to dedicated document.
adlerjohn Jun 6, 2020
ecda995
Fix commission calculation for validator.
adlerjohn Jun 6, 2020
4fd51b7
Remove some rationale from consensus document for reward distribution.
adlerjohn Jun 8, 2020
fa9307b
Add preamble to reward distribution doc, clearn up.
adlerjohn Jun 8, 2020
e1d624c
Clean up.
adlerjohn Jun 8, 2020
39bc623
Revamp reward distribution rationale.
adlerjohn Jun 9, 2020
6bba8c6
Fix state data structures for reward distribution.
adlerjohn Jun 9, 2020
6e036ad
Remove redundant entry from validator.
adlerjohn Jun 9, 2020
747b594
Remove other redundant entry from validator.
adlerjohn Jun 9, 2020
62f759c
Fix consensus rules for distributing rewards.
adlerjohn Jun 9, 2020
09f95d2
Cleanup.
adlerjohn Jun 9, 2020
5449c59
Update specs/consensus.md
adlerjohn Jun 9, 2020
e8cc2d3
Simplify naming of accounts tree.
adlerjohn Jun 10, 2020
e4005c8
Clean up.
adlerjohn Jun 10, 2020
8785e68
Clean up informal language.
adlerjohn Jun 10, 2020
70ac923
Refactor consensus rules for validators and delegations to use code s…
adlerjohn Jun 11, 2020
f1cba75
Refactor state tree to use a single unified tree with distinct subtrees.
adlerjohn Jun 11, 2020
a9d5149
Fix typo.
adlerjohn Jun 11, 2020
4fbff8a
Remove redundant state root definition.
adlerjohn Jun 11, 2020
4a50807
Remove redundant validator flag in accounts.
adlerjohn Jun 11, 2020
a8c17a7
Add protobuf definitions for state elements.
adlerjohn Jun 11, 2020
73de678
Add another subtree for inactive validators.
adlerjohn Jun 11, 2020
b4db7e3
Clean up.
adlerjohn Jun 11, 2020
2c4e44c
Fix typo.
adlerjohn Jun 11, 2020
4d87996
Move the active validator count to the active validators subtree.
adlerjohn Jun 15, 2020
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
17 changes: 14 additions & 3 deletions specs/consensus.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Consensus Rules
- [Constants](#constants)
- [Types](#types)
- [Reserved Namespace IDs](#reserved-namespace-ids)
- [Reserved State Subtree IDs](#reserved-state-subtree-ids)
- [Rewards and Penalties](#rewards-and-penalties)
- [Leader Selection](#leader-selection)
- [Fork Choice](#fork-choice)
Expand Down Expand Up @@ -41,12 +42,14 @@ Consensus Rules
| `GENESIS_COIN_COUNT` | `uint64` | `10**8` | `4u` | `(= 100000000)` Number of coins at genesis. |
| `UNBONDING_DURATION` | `uint32` | | `block` | Duration, in blocks, for unbonding a validator or delegation. |
| `MAX_VALIDATORS` | `uint16` | `64` | | Maximum number of active validators. |
| `STATE_SUBTREE_RESERVED_BYTES` | `uint64` | `1` | `byte` | Number of bytes reserved to identify state subtrees. |

## Types

| name | type |
| ------------- | -------- |
| `NamespaceID` | `uint64` |
| name | type |
| ---------------- | -------- |
| `NamespaceID` | `uint64` |
| `StateSubtreeID` | `byte` |

## Reserved Namespace IDs

Expand All @@ -57,6 +60,14 @@ Consensus Rules
| `EVIDENCE_NAMESPACE_ID` | `NamespaceID` | `0x0000000000000000000000000000000000000000000000000000000000000003` |
| `PARITY_SHARE_NAMESPACE_ID` | `NamespaceID` | `0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF` |

## Reserved State Subtree IDs

| name | type | value |
| ---------------------------- | ---------------- | ------ |
| `ACCOUNTS_SUBTREE_ID` | `StateSubtreeID` | `0x01` |
| `VALIDATORS_SUBTREE_ID` | `StateSubtreeID` | `0x02` |
Copy link
Member

@liamsi liamsi Jun 13, 2020

Choose a reason for hiding this comment

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

In tendermint, the header references current and next validators:
https://github.com/tendermint/tendermint/blob/206c814a8e64cb4b9eb2abbb2fdadc6933b28584/types/block.go#L352-L353

Should we have two subtrees for that? Or, further split the validator subtree? The number of validators should easily fit into that tree in any case.

Copy link
Member Author

Choose a reason for hiding this comment

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

This isn't needed with immediate execution. Regardless, the validator set for the current block is the next validator set of the previous block, so we don't need to maintain two trees. Only the next validator set is needed to be stored in the tree at any given time.

Copy link
Member

Choose a reason for hiding this comment

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

Only the next validator set is needed to be stored in the tree at any given time.

That makes sense. I'm wondering if there is a reason in tendermint for having both validator sets referenced. Might have to do with deferred execution (related tendermint/tendermint#2483) or maybe it is just for convenience 🤔

Copy link
Member

@liamsi liamsi Jun 16, 2020

Choose a reason for hiding this comment

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

Regardless, the validator set for the current block is the next validator set of the previous block,

How would a light client verify this property efficiently if only the next valset is stored & merkelized? Doesn't it need a commit to the next valst (of the previous block) that can be easyily verified on the current block (e.g. via a root included int the header) without recomputing the cur vals?

Copy link
Member Author

Choose a reason for hiding this comment

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

Transcluding conversation from Slack:

Under immediate execution, the next validator set is actually committed to in the last intermediate state root's active validator set subtree.

It might be worth it to have a dedicated field in the block header for this, and just making sure it matches up with the last intermediate state root's active validator subtree root.

Copy link
Member

Choose a reason for hiding this comment

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

@buchmann clarified here why both hashes are part of the header (and not just the state root/tree) in tendermint:
celestiaorg/celestia-core#3 (comment)

| `VALIDATOR_COUNT_SUBTREE_ID` | `StateSubtreeID` | `0x03` |
Copy link
Member

@liamsi liamsi Jun 13, 2020

Choose a reason for hiding this comment

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

Shouldn't this be part of the validator subtree (e.g the left-most branch in the validator subtree contains the count)? Why do we need this again? Isn't that implicitly given by the number of leaves in the subtree that aren't default values (i.e. no active validators).

Copy link
Member Author

@adlerjohn adlerjohn Jun 14, 2020

Choose a reason for hiding this comment

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

It could, but mangling multiple leaf types into a single tree (or subtree) makes things more complicated. E.g. you'd need subtree-specific logic if you want to parallelize updating the validator set and the validator count state. If they're in separate subtrees then you can have a single global whole-SMT-level process that handles updating subtrees in parallel.

The validator count is needed not for light nodes knowing they've downloaded the entire validator set, but for proving with a fraud proof that the number of active validators exceeds the maximum.

See other comment further down for some more on this.


## Rewards and Penalties

| name | type | value | unit | description |
Expand Down
80 changes: 47 additions & 33 deletions specs/data_structures.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ Data Structures
- [Message](#message)
- [State](#state)
- [Account](#account)
- [PeriodEntry](#periodentry)
- [Validator](#validator)
- [Delegation](#delegation)
- [Validator](#validator)
- [ActiveValidatorCount](#activevalidatorcount)
- [PeriodEntry](#periodentry)
- [Decimal](#decimal)
- [Consensus Parameters](#consensus-parameters)

Expand Down Expand Up @@ -480,14 +481,18 @@ enum VoteType : uint8_t {

# State

| name | type | description |
| --------------- | ------------------------- | ---------------------------- |
| `accountsRoot` | [HashDigest](#hashdigest) | Merkle root of account tree. |
| `numValidators` | `uint32` | Number of active validators. |
| name | type | description |
| ----------- | ------------------------- | -------------------------- |
| `stateRoot` | [HashDigest](#hashdigest) | Merkle root of state tree. |

The state of the LazyLedger chain contains only account balances and the validator set (which is extra metadata on top of the plain account balances).
The state of the LazyLedger chain is intentionally restricted to containing only account balances and the validator set metadata. One unified [Sparse Merkle Trees](#sparse-merkle-tree) is maintained for the entire chain state, the _state tree_.

One unified [Sparse Merkle Trees](#sparse-merkle-tree) is maintained for both account account balances and validator metadata, the _accounts tree_. The final state root is computed as the [hash](#hashdigest) of the accounts tree root and number of active validators. The latter is necessary to ensure light nodes can determine the entire validator set from a single state root commitment.
The state tree is separated into `2**(8*STATE_SUBTREE_RESERVED_BYTES)` subtrees, each of which can be used to store a different component of the state. This is done by slicing off the highest `STATE_SUBTREE_RESERVED_BYTES` bytes from the key and replacing them with the appropriate [reserved state subtree ID](consensus.md#reserved-state-subtree-ids). Reducing the key size within subtrees also reduces the collision resistance of keys by `8*STATE_SUBTREE_RESERVED_BYTES` bits, but this is not an issue due the number of bits removed being small.

Three subtrees are maintained:
1. [Accounts](#account)
1. [Active validator set](#validator)
1. [Active validator count](#activevalidatorcount)

## Account

Expand All @@ -496,19 +501,32 @@ One unified [Sparse Merkle Trees](#sparse-merkle-tree) is maintained for both ac
| `balance` | `uint64` | Coin balance. |
| `nonce` | `uint64` | Account nonce. Every outgoing transaction from this account increments the nonce. |
| `isValidator` | `bool` | Whether this account is a validator or not. Mutually exclusive with `isDelegating`. |
| `validatorInfo` | [Validator](#validator) | _Optional_, only if `isValidator` is set. Validator info. |
| `isDelegating` | `bool` | Whether this account is delegating its stake or not. Mutually exclusive with `isValidator`. |
| `delegationInfo` | [Delegation](#delegation) | _Optional_, only if `isDelegating` is set. Delegation info. |

In the accounts tree, accounts (i.e. leaves) are keyed by the [hash](#hashdigest) of their [address](#address).
In the accounts subtree, accounts (i.e. leaves) are keyed by the [hash](#hashdigest) of their [address](#address). The first byte is then replaced with `ACCOUNTS_SUBTREE_ID`.
Copy link
Member

Choose a reason for hiding this comment

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

Effectively, this means that the hash used in the subtree returns 31 bytes.

Copy link
Member Author

@adlerjohn adlerjohn Jun 14, 2020

Choose a reason for hiding this comment

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

Not the hash function in general, but specifically how the key is calculated, yes.

Copy link
Member

@liamsi liamsi Jun 14, 2020

Choose a reason for hiding this comment

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

If we say it's the hash function, this is easier to formalize. e.g. we can say the key for an account in the set Accs tree is computed as:
key := subtree_id || hash_st(address),
where hash_st could be any cryptographic hash function with 31 byte/248 bit output: hash_st: Accs -> {0,1}^248

Copy link
Member Author

@adlerjohn adlerjohn Jun 14, 2020

Choose a reason for hiding this comment

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

Hmm. I'm not really a fan because that would mean we need 1) a different hashing function for each subtree (not great, not terrible) and 2) to slice off the first byte of every single hashing operation. That cost will add up quickly, especially if doing proofs in smart contracts.

I'd much prefer just changing how the keys are calculated, which is a one-time calculation per leaf.

Copy link
Member

Choose a reason for hiding this comment

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

IMO It's just another way to writing down the same thing though (of course in 2) the slicing off could be done more efficiently by replacing the 1st byte instead; but this is just an implementation detail).


## PeriodEntry
## Delegation

| name | type | description |
| ------------ | -------- | ------------------------------------------------------------- |
| `rewardRate` | `uint64` | Rewards per unit of voting power accumulated so far, in `1u`. |
```C++
enum DelegationStatus : uint8_t {
Bonded = 1,
Unbonding = 2,
};
```

For explanation on entries, see the [reward distribution rationale document](../rationale/distributing_rewards.md).
| name | type | description |
| ----------------- | --------------------------- | --------------------------------------------------- |
| `status` | `DelegationStatus` | Status of this delegation. |
| `validator` | [Address](#address) | The validator being delegating to. |
| `stakedBalance` | `uint64` | Delegated stake, in `4u`. |
| `beginEntry` | [PeriodEntry](#periodentry) | Entry when delegation began. |
| `endEntry` | [PeriodEntry](#periodentry) | Entry when delegation ended (i.e. began unbonding). |
| `unbondingHeight` | `uint64` | Block height delegation began unbonding. |

Delegation objects represent a delegation. They have two statuses:
1. `Bonded`: This delegation is enabled for a `Queued` _or_ `Bonded` validator. Delegations to a `Queued` validator can be withdrawn immediately, while delegations for a `Bonded` validator must be unbonded first.
1. `Unbonding`: This delegation is unbonding. It will remain in this status for at least `UNBONDING_DURATION` blocks, and while unbonding may still be slashed. Once the unbonding duration has expired, the delegation can be withdrawn.

## Validator

Expand Down Expand Up @@ -540,27 +558,23 @@ Validator objects represent all the information needed to be keep track of a val
1. `Unbonding`: This validator is in the process of unbonding, which can be voluntary (the validator decided to stop being an active validator) or forced (the validator committed a slashable offence and was kicked from the active validator set). Validators will remain in this status for at least `UNBONDING_DURATION` blocks, and while unbonding may still be slashed.
1. `Unbonded`: This validator has completed its unbonding and has withdrawn its stake. The validator object will remain in this status until `delegatedCount` reaches zero, at which point it is destroyed.

## Delegation
In the validators subtree, validators are keyed by the [hash](#hashdigest) of their [address](#address). The first byte is then replaced with `VALIDATORS_SUBTREE_ID`. By construction, the validators subtree will be a subset of a mirror of the [accounts subtree](#account).

```C++
enum DelegationStatus : uint8_t {
Bonded = 1,
Unbonding = 2,
};
```
## ActiveValidatorCount

| name | type | description |
| ----------------- | --------------------------- | --------------------------------------------------- |
| `status` | `DelegationStatus` | Status of this delegation. |
| `validator` | [Address](#address) | The validator being delegating to. |
| `stakedBalance` | `uint64` | Delegated stake, in `4u`. |
| `beginEntry` | [PeriodEntry](#periodentry) | Entry when delegation began. |
| `endEntry` | [PeriodEntry](#periodentry) | Entry when delegation ended (i.e. began unbonding). |
| `unbondingHeight` | `uint64` | Block height delegation began unbonding. |
| name | type | description |
| --------------- | -------- | ---------------------------- |
| `numValidators` | `uint32` | Number of active validators. |

Delegation objects represent a delegation. They have two statuses:
1. `Bonded`: This delegation is enabled for a `Queued` _or_ `Bonded` validator. Delegations to a `Queued` validator can be withdrawn immediately, while delegations for a `Bonded` validator must be unbonded first.
1. `Unbonding`: This delegation is unbonding. It will remain in this status for at least `UNBONDING_DURATION` blocks, and while unbonding may still be slashed. Once the unbonding duration has expired, the delegation can be withdrawn.
Since the [validator set](#validator) is stored in a Sparse Merkle Tree, there is no compact way of proving that the number of active validators exceeds `MAX_VALIDATORS` without keeping track of the number of active validators. There is only a single leaf in the active validator count subtree, which is keyed with zero (i.e. `0x0000000000000000000000000000000000000000000000000000000000000000`), and the first byte replaced with `VALIDATOR_COUNT_SUBTREE_ID`.
Copy link
Member

Choose a reason for hiding this comment

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

imo this should be part of validator subtree:
f1cba75#r439773622

Copy link
Member Author

Choose a reason for hiding this comment

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

Unlike moving the inactive validator set into the accounts subtree, I might be okay with moving the validator count into the active validators subtree because it's guaranteed to be in a fixed location.

Copy link
Member

Choose a reason for hiding this comment

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

I think it semantically belongs to the validator subtree and also a single value tree seems weird.

Copy link
Member Author

Choose a reason for hiding this comment

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

Alright, will migrate the count into the active validators subtree.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed in 4d87996.


## PeriodEntry

| name | type | description |
| ------------ | -------- | ------------------------------------------------------------- |
| `rewardRate` | `uint64` | Rewards per unit of voting power accumulated so far, in `1u`. |

For explanation on entries, see the [reward distribution rationale document](../rationale/distributing_rewards.md).

## Decimal

Expand Down