github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/types/proposal.go (about)

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