You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

199 lines
6.0 KiB

  1. package types
  2. import (
  3. "errors"
  4. "fmt"
  5. "math/bits"
  6. "time"
  7. "github.com/tendermint/tendermint/internal/libs/protoio"
  8. tmbytes "github.com/tendermint/tendermint/libs/bytes"
  9. tmtime "github.com/tendermint/tendermint/libs/time"
  10. tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
  11. )
  12. var (
  13. ErrInvalidBlockPartSignature = errors.New("error invalid block part signature")
  14. ErrInvalidBlockPartHash = errors.New("error invalid block part hash")
  15. )
  16. // Proposal defines a block proposal for the consensus.
  17. // It refers to the block by BlockID field.
  18. // It must be signed by the correct proposer for the given Height/Round
  19. // to be considered valid. It may depend on votes from a previous round,
  20. // a so-called Proof-of-Lock (POL) round, as noted in the POLRound.
  21. // If POLRound >= 0, then BlockID corresponds to the block that is locked in POLRound.
  22. type Proposal struct {
  23. Type tmproto.SignedMsgType
  24. Height int64 `json:"height,string"`
  25. Round int32 `json:"round"` // there can not be greater than 2_147_483_647 rounds
  26. POLRound int32 `json:"pol_round"` // -1 if null.
  27. BlockID BlockID `json:"block_id"`
  28. Timestamp time.Time `json:"timestamp"`
  29. Signature []byte `json:"signature"`
  30. }
  31. // NewProposal returns a new Proposal.
  32. // If there is no POLRound, polRound should be -1.
  33. func NewProposal(height int64, round int32, polRound int32, blockID BlockID, ts time.Time) *Proposal {
  34. return &Proposal{
  35. Type: tmproto.ProposalType,
  36. Height: height,
  37. Round: round,
  38. BlockID: blockID,
  39. POLRound: polRound,
  40. Timestamp: tmtime.Canonical(ts),
  41. }
  42. }
  43. // ValidateBasic performs basic validation.
  44. func (p *Proposal) ValidateBasic() error {
  45. if p.Type != tmproto.ProposalType {
  46. return errors.New("invalid Type")
  47. }
  48. if p.Height < 0 {
  49. return errors.New("negative Height")
  50. }
  51. if p.Round < 0 {
  52. return errors.New("negative Round")
  53. }
  54. if p.POLRound < -1 {
  55. return errors.New("negative POLRound (exception: -1)")
  56. }
  57. if err := p.BlockID.ValidateBasic(); err != nil {
  58. return fmt.Errorf("wrong BlockID: %w", err)
  59. }
  60. // ValidateBasic above would pass even if the BlockID was empty:
  61. if !p.BlockID.IsComplete() {
  62. return fmt.Errorf("expected a complete, non-empty BlockID, got: %v", p.BlockID)
  63. }
  64. // NOTE: Timestamp validation is subtle and handled elsewhere.
  65. if len(p.Signature) == 0 {
  66. return errors.New("signature is missing")
  67. }
  68. if len(p.Signature) > MaxSignatureSize {
  69. return fmt.Errorf("signature is too big (max: %d)", MaxSignatureSize)
  70. }
  71. return nil
  72. }
  73. // IsTimely validates that the block timestamp is 'timely' according to the proposer-based timestamp algorithm.
  74. // To evaluate if a block is timely, its timestamp is compared to the local time of the validator along with the
  75. // configured Precision and MsgDelay parameters.
  76. // Specifically, a proposed block timestamp is considered timely if it is satisfies the following inequalities:
  77. //
  78. // localtime >= proposedBlockTime - Precision
  79. // localtime <= proposedBlockTime + MsgDelay + Precision
  80. //
  81. // For more information on the meaning of 'timely', see the proposer-based timestamp specification:
  82. // https://github.com/tendermint/tendermint/tree/master/spec/consensus/proposer-based-timestamp
  83. func (p *Proposal) IsTimely(recvTime time.Time, sp SynchronyParams, round int32) bool {
  84. // The message delay values are scaled as rounds progress.
  85. // Every 10 rounds, the message delay is doubled to allow consensus to
  86. // proceed in the case that the chosen value was too small for the given network conditions.
  87. // For more information and discussion on this mechanism, see the relevant github issue:
  88. // https://github.com/tendermint/spec/issues/371
  89. maxShift := bits.LeadingZeros64(uint64(sp.MessageDelay)) - 1
  90. nShift := int((round / 10))
  91. if nShift > maxShift {
  92. // if the number of 'doublings' would would overflow the size of the int, use the
  93. // maximum instead.
  94. nShift = maxShift
  95. }
  96. msgDelay := sp.MessageDelay * time.Duration(1<<nShift)
  97. // lhs is `proposedBlockTime - Precision` in the first inequality
  98. lhs := p.Timestamp.Add(-sp.Precision)
  99. // rhs is `proposedBlockTime + MsgDelay + Precision` in the second inequality
  100. rhs := p.Timestamp.Add(msgDelay).Add(sp.Precision)
  101. if recvTime.Before(lhs) || recvTime.After(rhs) {
  102. return false
  103. }
  104. return true
  105. }
  106. // String returns a string representation of the Proposal.
  107. //
  108. // 1. height
  109. // 2. round
  110. // 3. block ID
  111. // 4. POL round
  112. // 5. first 6 bytes of signature
  113. // 6. timestamp
  114. //
  115. // See BlockID#String.
  116. func (p *Proposal) String() string {
  117. return fmt.Sprintf("Proposal{%v/%v (%v, %v) %X @ %s}",
  118. p.Height,
  119. p.Round,
  120. p.BlockID,
  121. p.POLRound,
  122. tmbytes.Fingerprint(p.Signature),
  123. CanonicalTime(p.Timestamp))
  124. }
  125. // ProposalSignBytes returns the proto-encoding of the canonicalized Proposal,
  126. // for signing. Panics if the marshaling fails.
  127. //
  128. // The encoded Protobuf message is varint length-prefixed (using MarshalDelimited)
  129. // for backwards-compatibility with the Amino encoding, due to e.g. hardware
  130. // devices that rely on this encoding.
  131. //
  132. // See CanonicalizeProposal
  133. func ProposalSignBytes(chainID string, p *tmproto.Proposal) []byte {
  134. pb := CanonicalizeProposal(chainID, p)
  135. bz, err := protoio.MarshalDelimited(&pb)
  136. if err != nil {
  137. panic(err)
  138. }
  139. return bz
  140. }
  141. // ToProto converts Proposal to protobuf
  142. func (p *Proposal) ToProto() *tmproto.Proposal {
  143. if p == nil {
  144. return &tmproto.Proposal{}
  145. }
  146. pb := new(tmproto.Proposal)
  147. pb.BlockID = p.BlockID.ToProto()
  148. pb.Type = p.Type
  149. pb.Height = p.Height
  150. pb.Round = p.Round
  151. pb.PolRound = p.POLRound
  152. pb.Timestamp = p.Timestamp
  153. pb.Signature = p.Signature
  154. return pb
  155. }
  156. // FromProto sets a protobuf Proposal to the given pointer.
  157. // It returns an error if the proposal is invalid.
  158. func ProposalFromProto(pp *tmproto.Proposal) (*Proposal, error) {
  159. if pp == nil {
  160. return nil, errors.New("nil proposal")
  161. }
  162. p := new(Proposal)
  163. blockID, err := BlockIDFromProto(&pp.BlockID)
  164. if err != nil {
  165. return nil, err
  166. }
  167. p.BlockID = *blockID
  168. p.Type = pp.Type
  169. p.Height = pp.Height
  170. p.Round = pp.Round
  171. p.POLRound = pp.PolRound
  172. p.Timestamp = pp.Timestamp
  173. p.Signature = pp.Signature
  174. return p, p.ValidateBasic()
  175. }