Skip to content

Commit 81b3ba0

Browse files
committed
PoS content WIP
1 parent 97d7ea4 commit 81b3ba0

File tree

111 files changed

+15418
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

111 files changed

+15418
-2
lines changed
File renamed without changes.
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Bor Consensus
2+
3+
Bor consensus is inspired by Clique consensus: [https://eips.ethereum.org/EIPS/eip-225](https://eips.ethereum.org/EIPS/eip-225). Clique works with multiple pre-defined producers. All producers vote on new producers using Clique APIs. They take turns creating blocks.
4+
5+
Bor fetches new producers through span and sprint management mechanism.
6+
7+
## Validators
8+
9+
Polygon is a Proof-of-stake system. Anyone can stake their Matic token on Ethereum smart-contract, "staking contract", and become a validator for the system.
10+
11+
```jsx
12+
function stake(
13+
uint256 amount,
14+
uint256 heimdallFee,
15+
address signer,
16+
bool acceptDelegation
17+
) external;
18+
```
19+
20+
Once validators are active on Heimdall they get selected as producers through `bor` module.
21+
22+
Check Bor overview to understand span management more in details: [Bor Overview](https://www.notion.so/Bor-Overview-c8bdb110cd4d4090a7e1589ac1006bab)
23+
24+
## Span
25+
26+
A logically defined set of blocks for which a set of validators is chosen from among all the available validators. Heimdall provides span details through span-details APIs.
27+
28+
```go
29+
// HeimdallSpan represents span from heimdall APIs
30+
type HeimdallSpan struct {
31+
Span
32+
ValidatorSet ValidatorSet `json:"validator_set" yaml:"validator_set"`
33+
SelectedProducers []Validator `json:"selected_producers" yaml:"selected_producers"`
34+
ChainID string `json:"bor_chain_id" yaml:"bor_chain_id"`
35+
}
36+
37+
// Span represents a current bor span
38+
type Span struct {
39+
ID uint64 `json:"span_id" yaml:"span_id"`
40+
StartBlock uint64 `json:"start_block" yaml:"start_block"`
41+
EndBlock uint64 `json:"end_block" yaml:"end_block"`
42+
}
43+
44+
// Validator represents a volatile state for each Validator
45+
type Validator struct {
46+
ID uint64 `json:"ID"`
47+
Address common.Address `json:"signer"`
48+
VotingPower int64 `json:"power"`
49+
ProposerPriority int64 `json:"accum"`
50+
}
51+
```
52+
53+
Geth (In this case, Bor) uses block `snapshot` to store state data for each block, including consensus related data.
54+
55+
Each validator in span contains voting power. Based on their power, they get selected as block producers. Higher power, a higher probability of becoming block producers. Bor uses Tendermint's algorithm for the same. Source: [https://github.com/maticnetwork/bor/blob/master/consensus/bor/valset/validator_set.go](https://github.com/maticnetwork/bor/blob/master/consensus/bor/valset/validator_set.go)
56+
57+
## Sprint
58+
59+
A set of blocks within a span for which only a single block producer is chosen to produce blocks. The sprint size is a factor of span size. Bor uses `validatorSet` to get current proposer/producer for current sprint.
60+
61+
```go
62+
currentProposerForSprint := snap.ValidatorSet().Proposer
63+
```
64+
65+
Apart from the current proposer, Bor selects back-up producers.
66+
67+
## Authorizing a block
68+
69+
The producers in Bor also called signers, since to authorize a block for the network, the producer needs to sign the block's hash containing **everything except the signature itself**. This means that the hash contains every field of the header, and also the `extraData` with the exception of the 65-byte signature suffix.
70+
71+
This hash is signed using the standard `secp256k1` curve, and the resulting 65-byte signature is embedded into the `extraData` as the trailing 65-byte suffix.
72+
73+
Each signed block is assigned to a difficulty that puts weight on Block. In-turn signing weighs more (`DIFF_INTURN`) than out of turn one (`DIFF_NOTURN`).
74+
75+
### Authorization strategies
76+
77+
As long as producers conform to the above specs, they can authorize and distribute blocks as they see fit. The following suggested strategy will, however, reduce network traffic and small forks, so it’s a suggested feature:
78+
79+
- If a producer is allowed to sign a block (is on the authorized list)
80+
- Calculate the optimal signing time of the next block (parent + `Period`)
81+
- If the producer is in-turn, wait for the exact time to arrive, sign and broadcast immediately
82+
- If the producer is out-of-turn, delay signing by `wiggle`
83+
84+
This small strategy will ensure that the in-turn producer (who's block weighs more) has a slight advantage to sign and propagate versus the out-of-turn signers. Also, the scheme allows a bit of scale with an increase of the number of producers.
85+
86+
### Out-of-turn signing
87+
88+
Bor chooses multiple block producers as a backup when in-turn producer doesn't produce a block. This could happen for a variety of reasons like:
89+
90+
- Block producer node is down
91+
- The block producer is trying to withhold the block
92+
- The block producer is not producing a block intentionally.
93+
94+
When the above happens, Bor's backup mechanism kicks in.
95+
96+
At any point of time, the validators set is stored as an array sorted on the basis of their signer address. Assume, that the validator set is ordered as A, B, C, D and that it is C's turn to produce a block. If C doesn't produce a block within a sufficient amount of time, D becomes in turn to produce one. If D doesn't then A and then B.
97+
98+
However, since there will be some time before C produces and propagates a block, the backup validators will wait a certain amount of time before starting to produce a block. This time delay is called wiggle.
99+
100+
### Wiggle
101+
102+
Wiggle is the time that a producer should wait before starting to produce a block.
103+
104+
- Say the last block (n-1) was produced at time `t`.
105+
- We enforce a minimum time delay between the current and next block by a variable parameter `Period`.
106+
- In ideal conditions, C will wait for `Period` and then produce and propagate the block. Since block times in Polygon are being designed to be quite low (2-4s), the propagation delay is also assumed to be the same value as `Period`.
107+
- So if D doesn't see a new block in time `2 * Period`, D immediately starts producing a block. Specifically, D's wiggle time is defined as `2 * Period * (pos(d) - pos(c))` where `pos(d) = 3` and `pos(c) = 2` in the validator set. Assuming, `Period = 1`, wiggle for D is 2s.
108+
- Now if D also doesn't produce a block, A will start producing one when the wiggle time of `2 * Period * (pos(a) + len(validatorSet) - pos(c)) = 4s` has elapsed.
109+
- Simmilary, wiggle for C is `6s`
110+
111+
### Resolving forks
112+
113+
While the above mechanism adds to the robustness of chain to a certain extent, it introduces the possibility of forks. It could actually be possible that C produced a block, but there was a larger than expected delay in propagation and hence D also produced a block, so that leads to at least 2 forks.
114+
115+
The resolution is simple - choose the chain with higher difficulty. But then the question is how do we define difficulty of a block in our setup?
116+
117+
### Difficulty
118+
119+
- The difficulty for a block that is produced by an in-turn signer (say c) is defined to be the highest = `len(validatorSet)`.
120+
- Since D is the producer who is next in line; if and when the situation arises that D is producing the block; the difficulty for the block will be defined just like in wiggle as `len(validatorSet) - (pos(d) - pos(c))` which is `len(validatorSet) - 1`
121+
- Difficulty for block being produced by A while acting as a backup becomes `len(validatorSet) - (pos(a) + len(validatorSet) - pos(c))` which is `2`
122+
123+
Now having defined the difficulty of each block, the difficulty of a fork is simply the sum of the difficulties of the blocks in that fork. In the case when a fork has to be chosen, the one with higher difficulty is chosen, since that is a reflection of the fact that blocks were produced by in-turn block producers. This is simply to provide some sense of finality to the user on Bor.
124+
125+
## View Change
126+
127+
After each span, Bor changes view. It means that it fetches new producers for the next span.
128+
129+
### Commit span
130+
131+
When the current span is about to end (specifically at the end of the second-last sprint in the span), Bor pulls a new span from Heimdall. This is a simple HTTP call to the Heimdall node. Once this data is fetched, a `commitSpan` call is made to the BorValidatorSet genesis contract through System call.
132+
133+
Bor also sets producers bytes into the header of the block. This is necessary while fast-syncing Bor. During fast-sync, Bor syncs headers in bulk and validates if blocks are created by authorized producers.
134+
135+
At the start of each Sprint, Bor fetches header bytes from the previous header for next producers and starts creating blocks based on `ValidatorSet` algorithm.
136+
137+
Here is how header looks like for a block:
138+
139+
```js
140+
header.Extra = header.Vanity + header.ProducerBytes /* optional */ + header.Seal
141+
```
142+
143+
<img src={useBaseUrl("img/Bor/header-bytes.svg")} />
144+
145+
## State sync from Ethereum Chain
146+
147+
Bor provides a mechanism where some specific events on the main Ethereum chain are relayed to Bor.
148+
149+
1. Any contract on Ethereum may call [syncState](https://github.com/maticnetwork/contracts/blob/develop/contracts/root/stateSyncer/StateSender.sol#L33) in `StateSender.sol`. This call emits `StateSynced` event: https://github.com/maticnetwork/contracts/blob/develop/contracts/root/stateSyncer/StateSender.sol#L38
150+
151+
```js
152+
event StateSynced(uint256 indexed id, address indexed contractAddress, bytes data)
153+
```
154+
155+
2. Heimdall listens to these events and calls `function proposeState(uint256 stateId)` in `StateReceiver.sol` - thus acting as a store for the pending state change ids. Note that the `proposeState` transaction will be processed even with a 0 gas fee as long as it is made by one of the validators in the current validator set: https://github.com/maticnetwork/genesis-contracts/blob/master/contracts/StateReceiver.sol#L24
156+
157+
3. At the start of every sprint, Bor pulls the details about the pending state changes using the states from Heimdall and commits them to the Bor state using a system call. See `commitState` here: https://github.com/maticnetwork/genesis-contracts/blob/f85d0409d2a99dff53617ad5429101d9937e3fc3/contracts/StateReceiver.sol#L41

0 commit comments

Comments
 (0)