-
Notifications
You must be signed in to change notification settings - Fork 184
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial implementation of Tendermint
- Loading branch information
1 parent
71c7ca2
commit c0b79c3
Showing
7 changed files
with
1,985 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package tendermint | ||
|
||
// Todo: Signature over the messages needs to be handled somewhere. There are 2 options: | ||
// 1. Add the signature to each message and extend the Validator Set interface to include VerifyMessageSignature | ||
// method. | ||
// 2. The P2P layer signs the message before gossiping to other validators and verifies the signature before passing | ||
// the message to the consensus engine. | ||
// The benefit of P2P layer handling the verification of the signature is the that the consensus layer can assume | ||
// the message is from a validator in the validator set. However, this means that the P2P layer would need to be aware | ||
// of the validator set and would need access to the blockchain which may not be a good idea. | ||
|
||
type Message[V Hashable[H], H Hash, A Addr] interface { | ||
Proposal[V, H, A] | Prevote[H, A] | Precommit[H, A] | ||
} | ||
|
||
type Proposal[V Hashable[H], H Hash, A Addr] struct { | ||
Height uint | ||
Round uint | ||
ValidRound *uint | ||
Value *V | ||
|
||
Sender A | ||
} | ||
|
||
type Prevote[H Hash, A Addr] struct { | ||
Vote[H, A] | ||
} | ||
|
||
type Precommit[H Hash, A Addr] struct { | ||
Vote[H, A] | ||
} | ||
|
||
type Vote[H Hash, A Addr] struct { | ||
Height uint | ||
Round uint | ||
ID *H | ||
|
||
Sender A | ||
} | ||
|
||
// messages keep tracks of all the proposals, prevotes, precommits by creating a map structure as follows: | ||
// height->round->address->[]Message | ||
|
||
// Todo: would the following representation of message be better: | ||
// | ||
// height -> round -> address -> ID -> Message | ||
// How would we keep track of nil votes? In golan map key cannot be nil. | ||
// It is not easy to calculate a zero value when dealing with generics. | ||
type messages[V Hashable[H], H Hash, A Addr] struct { | ||
proposals map[uint]map[uint]map[A][]Proposal[V, H, A] | ||
prevotes map[uint]map[uint]map[A][]Prevote[H, A] | ||
precommits map[uint]map[uint]map[A][]Precommit[H, A] | ||
} | ||
|
||
func newMessages[V Hashable[H], H Hash, A Addr]() messages[V, H, A] { | ||
return messages[V, H, A]{ | ||
proposals: make(map[uint]map[uint]map[A][]Proposal[V, H, A]), | ||
prevotes: make(map[uint]map[uint]map[A][]Prevote[H, A]), | ||
precommits: make(map[uint]map[uint]map[A][]Precommit[H, A]), | ||
} | ||
} | ||
|
||
// Todo: ensure duplicated messages are ignored. | ||
func (m *messages[V, H, A]) addProposal(p Proposal[V, H, A]) { | ||
if _, ok := m.proposals[p.Height]; !ok { | ||
m.proposals[p.Height] = make(map[uint]map[A][]Proposal[V, H, A]) | ||
} | ||
|
||
if _, ok := m.proposals[p.Height][p.Round]; !ok { | ||
m.proposals[p.Height][p.Round] = make(map[A][]Proposal[V, H, A]) | ||
} | ||
|
||
sendersProposals, ok := m.proposals[p.Height][p.Round][p.Sender] | ||
if !ok { | ||
sendersProposals = []Proposal[V, H, A]{} | ||
} | ||
|
||
m.proposals[p.Height][p.Round][p.Sender] = append(sendersProposals, p) | ||
} | ||
|
||
func (m *messages[V, H, A]) addPrevote(p Prevote[H, A]) { | ||
if _, ok := m.prevotes[p.Height]; !ok { | ||
m.prevotes[p.Height] = make(map[uint]map[A][]Prevote[H, A]) | ||
} | ||
|
||
if _, ok := m.prevotes[p.Height][p.Round]; !ok { | ||
m.prevotes[p.Height][p.Round] = make(map[A][]Prevote[H, A]) | ||
} | ||
|
||
sendersPrevotes, ok := m.prevotes[p.Height][p.Round][p.Sender] | ||
if !ok { | ||
sendersPrevotes = []Prevote[H, A]{} | ||
} | ||
|
||
m.prevotes[p.Height][p.Round][p.Sender] = append(sendersPrevotes, p) | ||
} | ||
|
||
func (m *messages[V, H, A]) addPrecommit(p Precommit[H, A]) { | ||
if _, ok := m.precommits[p.Height]; !ok { | ||
m.precommits[p.Height] = make(map[uint]map[A][]Precommit[H, A]) | ||
} | ||
|
||
if _, ok := m.precommits[p.Height][p.Round]; !ok { | ||
m.precommits[p.Height][p.Round] = make(map[A][]Precommit[H, A]) | ||
} | ||
|
||
sendersPrecommits, ok := m.precommits[p.Height][p.Round][p.Sender] | ||
if !ok { | ||
sendersPrecommits = []Precommit[H, A]{} | ||
} | ||
|
||
m.precommits[p.Height][p.Round][p.Sender] = append(sendersPrecommits, p) | ||
} | ||
|
||
func (m *messages[V, H, A]) allMessages(h, r uint) (map[A][]Proposal[V, H, A], map[A][]Prevote[H, A], | ||
map[A][]Precommit[H, A], | ||
) { | ||
// Todo: Should they be copied? | ||
return m.proposals[h][r], m.prevotes[h][r], m.precommits[h][r] | ||
} | ||
|
||
func (m *messages[V, H, A]) deleteHeightMessages(h uint) { | ||
delete(m.proposals, h) | ||
delete(m.prevotes, h) | ||
delete(m.precommits, h) | ||
} | ||
|
||
func (m *messages[V, H, A]) deleteRoundMessages(h, r uint) { | ||
delete(m.proposals[h], r) | ||
delete(m.prevotes[h], r) | ||
delete(m.precommits[h], r) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package tendermint | ||
|
||
import ( | ||
"fmt" | ||
"maps" | ||
"slices" | ||
) | ||
|
||
func (t *Tendermint[V, H, A]) handlePrecommit(p Precommit[H, A]) { | ||
if p.Height < t.state.height { | ||
return | ||
} | ||
|
||
if p.Height > t.state.height { | ||
if p.Height-t.state.height > maxFutureHeight { | ||
return | ||
} | ||
|
||
if p.Round > maxFutureRound { | ||
return | ||
} | ||
|
||
t.futureMessagesMu.Lock() | ||
defer t.futureMessagesMu.Unlock() | ||
t.futureMessages.addPrecommit(p) | ||
return | ||
} | ||
|
||
if p.Round > t.state.round { | ||
if p.Round-t.state.round > maxFutureRound { | ||
return | ||
} | ||
|
||
t.futureMessagesMu.Lock() | ||
defer t.futureMessagesMu.Unlock() | ||
|
||
t.futureMessages.addPrecommit(p) | ||
|
||
/* | ||
Check upon condition line 55: | ||
55: upon f + 1 {∗, h_p, round, ∗, ∗} with round > round_p do | ||
56: StartRound(round) | ||
*/ | ||
|
||
t.line55(p.Round) | ||
return | ||
} | ||
|
||
fmt.Println("got precommmit") | ||
t.messages.addPrecommit(p) | ||
|
||
proposalsForHR, _, precommitsForHR := t.messages.allMessages(p.Height, p.Round) | ||
|
||
/* | ||
Check the upon condition on line 49: | ||
49: upon {PROPOSAL, h_p, r, v, *} from proposer(h_p, r) AND 2f + 1 {PRECOMMIT, h_p, r, id(v)} while decision_p[h_p] = nil do | ||
50: if valid(v) then | ||
51: decisionp[hp] = v | ||
52: h_p ← h_p + 1 | ||
53: reset lockedRound_p, lockedValue_p, validRound_p and validValue_p to initial values and empty message log | ||
54: StartRound(0) | ||
Fetching the relevant proposal implies the sender of the proposal was the proposer for that | ||
height and round. Also, since only the proposals with valid value are added to the message set, the | ||
validity of the proposal can be skipped. | ||
There is no need to check decision_p[h_p] = nil since it is implied that decision are made | ||
sequentially, i.e. x, x+1, x+2... . | ||
*/ | ||
|
||
if p.ID != nil { | ||
var ( | ||
proposal *Proposal[V, H, A] | ||
precommits []Precommit[H, A] | ||
vals []A | ||
) | ||
|
||
for _, prop := range proposalsForHR[t.validators.Proposer(p.Height, p.Round)] { | ||
if (*prop.Value).Hash() == *p.ID { | ||
propCopy := prop | ||
proposal = &propCopy | ||
} | ||
} | ||
|
||
for addr, valPrecommits := range precommitsForHR { | ||
for _, v := range valPrecommits { | ||
if *v.ID == *p.ID { | ||
precommits = append(precommits, v) | ||
vals = append(vals, addr) | ||
} | ||
} | ||
} | ||
if proposal != nil && t.validatorSetVotingPower(vals) >= q(t.validators.TotalVotingPower(p.Height)) { | ||
t.blockchain.Commit(t.state.height, *proposal.Value, precommits) | ||
|
||
t.messages.deleteHeightMessages(t.state.height) | ||
t.state.height++ | ||
t.startRound(0) | ||
|
||
return | ||
} | ||
} | ||
|
||
/* | ||
Check the upon condition on line 47: | ||
47: upon 2f + 1 {PRECOMMIT, h_p, round_p, ∗} for the first time do | ||
48: schedule OnTimeoutPrecommit(h_p , round_p) to be executed after timeoutPrecommit(round_p) | ||
*/ | ||
|
||
vals := slices.Collect(maps.Keys(precommitsForHR)) | ||
if p.Round == t.state.round && !t.state.line47Executed && | ||
t.validatorSetVotingPower(vals) >= q(t.validators.TotalVotingPower(p.Height)) { | ||
t.scheduleTimeout(t.timeoutPrecommit(p.Round), precommit, p.Height, p.Round) | ||
t.state.line47Executed = true | ||
} | ||
} |
Oops, something went wrong.