package consensus import ( "encoding/binary" "encoding/gob" "errors" "github.com/tutus-one/tutus-consensus" "github.com/tutus-one/tutus-consensus/internal/crypto" ) type ( recoveryMessage struct { preparationHash *crypto.Uint256 preparationPayloads []preparationCompact preCommitPayloads []preCommitCompact commitPayloads []commitCompact changeViewPayloads []changeViewCompact prepareRequest dbft.PrepareRequest[crypto.Uint256] } // recoveryMessageAux is an auxiliary structure for recoveryMessage encoding. recoveryMessageAux struct { PreparationPayloads []preparationCompact PreCommitPayloads []preCommitCompact CommitPayloads []commitCompact ChangeViewPayloads []changeViewCompact } ) var _ dbft.RecoveryMessage[crypto.Uint256] = (*recoveryMessage)(nil) // PreparationHash implements RecoveryMessage interface. func (m *recoveryMessage) PreparationHash() *crypto.Uint256 { return m.preparationHash } // AddPayload implements RecoveryMessage interface. func (m *recoveryMessage) AddPayload(p dbft.ConsensusPayload[crypto.Uint256]) { switch p.Type() { case dbft.PrepareRequestType: m.prepareRequest = p.GetPrepareRequest() prepHash := p.Hash() m.preparationHash = &prepHash case dbft.PrepareResponseType: m.preparationPayloads = append(m.preparationPayloads, preparationCompact{ ValidatorIndex: p.ValidatorIndex(), }) case dbft.ChangeViewType: m.changeViewPayloads = append(m.changeViewPayloads, changeViewCompact{ ValidatorIndex: p.ValidatorIndex(), OriginalViewNumber: p.ViewNumber(), Timestamp: 0, }) case dbft.PreCommitType: pcc := preCommitCompact{ ViewNumber: p.ViewNumber(), ValidatorIndex: p.ValidatorIndex(), Data: p.GetPreCommit().Data(), } m.preCommitPayloads = append(m.preCommitPayloads, pcc) case dbft.CommitType: cc := commitCompact{ ViewNumber: p.ViewNumber(), ValidatorIndex: p.ValidatorIndex(), } copy(cc.Signature[:], p.GetCommit().Signature()) m.commitPayloads = append(m.commitPayloads, cc) default: // Other types (recoveries) can't be packed into recovery. } } func fromPayload(t dbft.MessageType, recovery dbft.ConsensusPayload[crypto.Uint256], p Serializable) *Payload { return &Payload{ message: message{ cmType: t, viewNumber: recovery.ViewNumber(), payload: p, }, height: recovery.Height(), } } // GetPrepareRequest implements RecoveryMessage interface. func (m *recoveryMessage) GetPrepareRequest(p dbft.ConsensusPayload[crypto.Uint256], _ []dbft.PublicKey, ind uint16) dbft.ConsensusPayload[crypto.Uint256] { if m.prepareRequest == nil { return nil } req := fromPayload(dbft.PrepareRequestType, p, &prepareRequest{ // prepareRequest.Timestamp() here returns nanoseconds-precision value, so convert it to seconds again timestamp: nanoSecToSec(m.prepareRequest.Timestamp()), nonce: m.prepareRequest.Nonce(), transactionHashes: m.prepareRequest.TransactionHashes(), }) req.SetValidatorIndex(ind) return req } // GetPrepareResponses implements RecoveryMessage interface. func (m *recoveryMessage) GetPrepareResponses(p dbft.ConsensusPayload[crypto.Uint256], _ []dbft.PublicKey) []dbft.ConsensusPayload[crypto.Uint256] { if m.preparationHash == nil { return nil } payloads := make([]dbft.ConsensusPayload[crypto.Uint256], len(m.preparationPayloads)) for i, resp := range m.preparationPayloads { payloads[i] = fromPayload(dbft.PrepareResponseType, p, &prepareResponse{ preparationHash: *m.preparationHash, }) payloads[i].SetValidatorIndex(resp.ValidatorIndex) } return payloads } // GetChangeViews implements RecoveryMessage interface. func (m *recoveryMessage) GetChangeViews(p dbft.ConsensusPayload[crypto.Uint256], _ []dbft.PublicKey) []dbft.ConsensusPayload[crypto.Uint256] { payloads := make([]dbft.ConsensusPayload[crypto.Uint256], len(m.changeViewPayloads)) for i, cv := range m.changeViewPayloads { payloads[i] = fromPayload(dbft.ChangeViewType, p, &changeView{ newViewNumber: cv.OriginalViewNumber + 1, timestamp: cv.Timestamp, }) payloads[i].SetValidatorIndex(cv.ValidatorIndex) } return payloads } // GetPreCommits implements RecoveryMessage interface. func (m *recoveryMessage) GetPreCommits(p dbft.ConsensusPayload[crypto.Uint256], _ []dbft.PublicKey) []dbft.ConsensusPayload[crypto.Uint256] { payloads := make([]dbft.ConsensusPayload[crypto.Uint256], len(m.preCommitPayloads)) for i, c := range m.preCommitPayloads { payloads[i] = fromPayload(dbft.PreCommitType, p, &preCommit{magic: binary.BigEndian.Uint32(c.Data)}) payloads[i].SetValidatorIndex(c.ValidatorIndex) } return payloads } // GetCommits implements RecoveryMessage interface. func (m *recoveryMessage) GetCommits(p dbft.ConsensusPayload[crypto.Uint256], _ []dbft.PublicKey) []dbft.ConsensusPayload[crypto.Uint256] { payloads := make([]dbft.ConsensusPayload[crypto.Uint256], len(m.commitPayloads)) for i, c := range m.commitPayloads { payloads[i] = fromPayload(dbft.CommitType, p, &commit{signature: c.Signature}) payloads[i].SetValidatorIndex(c.ValidatorIndex) } return payloads } // EncodeBinary implements Serializable interface. func (m recoveryMessage) EncodeBinary(w *gob.Encoder) error { hasReq := m.prepareRequest != nil if err := w.Encode(hasReq); err != nil { return err } if hasReq { if err := m.prepareRequest.(Serializable).EncodeBinary(w); err != nil { return err } } else { if m.preparationHash == nil { if err := w.Encode(0); err != nil { return err } } else { if err := w.Encode(crypto.Uint256Size); err != nil { return err } if err := w.Encode(m.preparationHash); err != nil { return err } } } return w.Encode(&recoveryMessageAux{ PreparationPayloads: m.preparationPayloads, CommitPayloads: m.commitPayloads, ChangeViewPayloads: m.changeViewPayloads, }) } // DecodeBinary implements Serializable interface. func (m *recoveryMessage) DecodeBinary(r *gob.Decoder) error { var hasReq bool if err := r.Decode(&hasReq); err != nil { return err } if hasReq { m.prepareRequest = new(prepareRequest) if err := m.prepareRequest.(Serializable).DecodeBinary(r); err != nil { return err } } else { var l int if err := r.Decode(&l); err != nil { return err } if l != 0 { if l == crypto.Uint256Size { m.preparationHash = new(crypto.Uint256) if err := r.Decode(m.preparationHash); err != nil { return err } } else { return errors.New("wrong crypto.Uint256 length") } } else { m.preparationHash = nil } } aux := new(recoveryMessageAux) if err := r.Decode(aux); err != nil { return err } m.preparationPayloads = aux.PreparationPayloads if m.preparationPayloads == nil { m.preparationPayloads = []preparationCompact{} } m.commitPayloads = aux.CommitPayloads if m.commitPayloads == nil { m.commitPayloads = []commitCompact{} } m.changeViewPayloads = aux.ChangeViewPayloads if m.changeViewPayloads == nil { m.changeViewPayloads = []changeViewCompact{} } return nil }