tutus-consensus/send.go

237 lines
5.8 KiB
Go

package dbft
import (
"fmt"
"go.uber.org/zap"
)
func (d *DBFT[H]) broadcast(msg ConsensusPayload[H]) {
d.Logger.Debug("broadcasting message",
zap.Stringer("type", msg.Type()),
zap.Uint32("height", d.BlockIndex),
zap.Uint("view", uint(d.ViewNumber)))
msg.SetValidatorIndex(uint16(d.MyIndex))
d.Broadcast(msg)
}
func (c *Context[H]) makePrepareRequest(force bool) ConsensusPayload[H] {
if !c.Fill(force) {
return nil
}
req := c.Config.NewPrepareRequest(c.Timestamp, c.Nonce, c.TransactionHashes)
return c.Config.NewConsensusPayload(c, PrepareRequestType, req)
}
func (d *DBFT[H]) sendPrepareRequest(force bool) {
msg := d.makePrepareRequest(force)
if msg == ConsensusPayload[H](nil) {
d.subscribeForTransactions()
// Try one more time since there's a tiny race between an attempt to
// construct prepare request and transactions subscription.
msg = d.makePrepareRequest(force)
if msg == ConsensusPayload[H](nil) {
delay := d.maxTimePerBlock - d.timePerBlock
d.changeTimer(delay)
return
}
}
d.unsubscribeFromTransactions()
d.PreparationPayloads[d.MyIndex] = msg
d.broadcast(msg)
d.prepareSentTime = d.Timer.Now()
delay := d.timePerBlock << (d.ViewNumber + 1)
if d.ViewNumber == 0 {
delay -= d.timePerBlock
}
d.Logger.Info("sending PrepareRequest", zap.Uint32("height", d.BlockIndex), zap.Uint("view", uint(d.ViewNumber)))
d.changeTimer(delay)
d.checkPrepare()
}
func (c *Context[H]) makeChangeView(ts uint64, reason ChangeViewReason) ConsensusPayload[H] {
cv := c.Config.NewChangeView(c.ViewNumber+1, reason, ts)
msg := c.Config.NewConsensusPayload(c, ChangeViewType, cv)
c.ChangeViewPayloads[c.MyIndex] = msg
return msg
}
func (d *DBFT[H]) sendChangeView(reason ChangeViewReason) {
if d.Context.WatchOnly() {
return
}
newView := d.ViewNumber + 1
d.changeTimer(d.timePerBlock << (newView + 1))
nc := d.CountCommitted()
nf := d.CountFailed()
if reason == CVTimeout && nc+nf > d.F() {
d.Logger.Info("skip change view", zap.Int("nc", nc), zap.Int("nf", nf))
d.sendRecoveryRequest()
return
}
// Timeout while missing transactions, set the real reason.
if !d.hasAllTransactions() && reason == CVTimeout {
reason = CVTxNotFound
}
d.Logger.Info("request change view",
zap.Int("view", int(d.ViewNumber)),
zap.Uint32("height", d.BlockIndex),
zap.Stringer("reason", reason),
zap.Int("new_view", int(newView)),
zap.Int("nc", nc),
zap.Int("nf", nf))
msg := d.makeChangeView(uint64(d.Timer.Now().UnixNano()), reason)
d.StopTxFlow()
d.broadcast(msg)
d.checkChangeView(newView)
}
func (c *Context[H]) makePrepareResponse() ConsensusPayload[H] {
resp := c.Config.NewPrepareResponse(c.PreparationPayloads[c.PrimaryIndex].Hash())
msg := c.Config.NewConsensusPayload(c, PrepareResponseType, resp)
c.PreparationPayloads[c.MyIndex] = msg
return msg
}
func (d *DBFT[H]) sendPrepareResponse() {
msg := d.makePrepareResponse()
d.Logger.Info("sending PrepareResponse", zap.Uint32("height", d.BlockIndex), zap.Uint("view", uint(d.ViewNumber)))
d.StopTxFlow()
d.broadcast(msg)
}
func (c *Context[H]) makePreCommit() (ConsensusPayload[H], error) {
if msg := c.PreCommitPayloads[c.MyIndex]; msg != nil {
return msg, nil
}
if preB := c.CreatePreBlock(); preB != nil {
var preData []byte
if err := preB.SetData(c.Priv); err == nil {
preData = preB.Data()
} else {
return nil, fmt.Errorf("PreCommit data construction failed: %w", err)
}
preCommit := c.Config.NewPreCommit(preData)
return c.Config.NewConsensusPayload(c, PreCommitType, preCommit), nil
}
return nil, fmt.Errorf("failed to construct PreBlock")
}
func (c *Context[H]) makeCommit() (ConsensusPayload[H], error) {
if msg := c.CommitPayloads[c.MyIndex]; msg != nil {
return msg, nil
}
if b := c.MakeHeader(); b != nil {
var sign []byte
if err := b.Sign(c.Priv); err == nil {
sign = b.Signature()
} else {
return nil, fmt.Errorf("header signing failed: %w", err)
}
commit := c.Config.NewCommit(sign)
return c.Config.NewConsensusPayload(c, CommitType, commit), nil
}
return nil, fmt.Errorf("failed to construct Header")
}
func (d *DBFT[H]) sendPreCommit() {
msg, err := d.makePreCommit()
if err != nil {
d.Logger.Error("failed to construct PreCommit", zap.Error(err))
return
}
d.PreCommitPayloads[d.MyIndex] = msg
d.Logger.Info("sending PreCommit", zap.Uint32("height", d.BlockIndex), zap.Uint("view", uint(d.ViewNumber)))
d.broadcast(msg)
}
func (d *DBFT[H]) sendCommit() {
msg, err := d.makeCommit()
if err != nil {
d.Logger.Error("failed to construct Commit", zap.Error(err))
return
}
d.CommitPayloads[d.MyIndex] = msg
d.Logger.Info("sending Commit", zap.Uint32("height", d.BlockIndex), zap.Uint("view", uint(d.ViewNumber)))
d.broadcast(msg)
}
func (d *DBFT[H]) sendRecoveryRequest() {
// If we're here, something is wrong, we either missing some messages or
// transactions or both, so re-request missing transactions here too.
if d.RequestSentOrReceived() && !d.hasAllTransactions() {
d.processMissingTx()
}
req := d.NewRecoveryRequest(uint64(d.Timer.Now().UnixNano()))
d.broadcast(d.NewConsensusPayload(&d.Context, RecoveryRequestType, req))
}
func (c *Context[H]) makeRecoveryMessage() ConsensusPayload[H] {
recovery := c.Config.NewRecoveryMessage()
for _, p := range c.PreparationPayloads {
if p != nil {
recovery.AddPayload(p)
}
}
cv := c.LastChangeViewPayloads
// if byte(msg.ViewNumber) == c.ViewNumber {
// cv = c.changeViewPayloads
// }
for _, p := range cv {
if p != nil {
recovery.AddPayload(p)
}
}
if c.PreCommitSent() {
for _, p := range c.PreCommitPayloads {
if p != nil {
recovery.AddPayload(p)
}
}
}
if c.CommitSent() {
for _, p := range c.CommitPayloads {
if p != nil {
recovery.AddPayload(p)
}
}
}
return c.Config.NewConsensusPayload(c, RecoveryMessageType, recovery)
}
func (d *DBFT[H]) sendRecoveryMessage() {
d.broadcast(d.makeRecoveryMessage())
}