package types import ( "bytes" "errors" "fmt" "time" tbytes "github.com/tendermint/tendermint/libs/bytes" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) // Info about the status of the light client type LightClientInfo struct { PrimaryID string `json:"primaryID"` WitnessesID []string `json:"witnessesID"` NumPeers int `json:"number_of_peers,string"` LastTrustedHeight int64 `json:"last_trusted_height,string"` LastTrustedHash tbytes.HexBytes `json:"last_trusted_hash"` LatestBlockTime time.Time `json:"latest_block_time"` TrustingPeriod string `json:"trusting_period"` // Boolean that reflects whether LatestBlockTime + trusting period is before // time.Now() (time when /status is called) TrustedBlockExpired bool `json:"trusted_block_expired"` } // LightBlock is a SignedHeader and a ValidatorSet. // It is the basis of the light client type LightBlock struct { *SignedHeader `json:"signed_header"` ValidatorSet *ValidatorSet `json:"validator_set"` } // ValidateBasic checks that the data is correct and consistent // // This does no verification of the signatures func (lb LightBlock) ValidateBasic(chainID string) error { if lb.SignedHeader == nil { return errors.New("missing signed header") } if lb.ValidatorSet == nil { return errors.New("missing validator set") } if err := lb.SignedHeader.ValidateBasic(chainID); err != nil { return fmt.Errorf("invalid signed header: %w", err) } if err := lb.ValidatorSet.ValidateBasic(); err != nil { return fmt.Errorf("invalid validator set: %w", err) } // make sure the validator set is consistent with the header if valSetHash := lb.ValidatorSet.Hash(); !bytes.Equal(lb.SignedHeader.ValidatorsHash, valSetHash) { return fmt.Errorf("expected validator hash of header to match validator set hash (%X != %X)", lb.SignedHeader.ValidatorsHash, valSetHash, ) } return nil } // String returns a string representation of the LightBlock func (lb LightBlock) String() string { return lb.StringIndented("") } // StringIndented returns an indented string representation of the LightBlock // // SignedHeader // ValidatorSet func (lb LightBlock) StringIndented(indent string) string { return fmt.Sprintf(`LightBlock{ %s %v %s %v %s}`, indent, lb.SignedHeader.StringIndented(indent+" "), indent, lb.ValidatorSet.StringIndented(indent+" "), indent) } // ToProto converts the LightBlock to protobuf func (lb *LightBlock) ToProto() (*tmproto.LightBlock, error) { if lb == nil { return nil, nil } lbp := new(tmproto.LightBlock) var err error if lb.SignedHeader != nil { lbp.SignedHeader = lb.SignedHeader.ToProto() } if lb.ValidatorSet != nil { lbp.ValidatorSet, err = lb.ValidatorSet.ToProto() if err != nil { return nil, err } } return lbp, nil } // LightBlockFromProto converts from protobuf back into the Lightblock. // An error is returned if either the validator set or signed header are invalid func LightBlockFromProto(pb *tmproto.LightBlock) (*LightBlock, error) { if pb == nil { return nil, errors.New("nil light block") } lb := new(LightBlock) if pb.SignedHeader != nil { sh, err := SignedHeaderFromProto(pb.SignedHeader) if err != nil { return nil, err } lb.SignedHeader = sh } if pb.ValidatorSet != nil { vals, err := ValidatorSetFromProto(pb.ValidatorSet) if err != nil { return nil, err } lb.ValidatorSet = vals } return lb, nil } //----------------------------------------------------------------------------- // SignedHeader is a header along with the commits that prove it. type SignedHeader struct { *Header `json:"header"` Commit *Commit `json:"commit"` } // ValidateBasic does basic consistency checks and makes sure the header // and commit are consistent. // // NOTE: This does not actually check the cryptographic signatures. Make sure // to use a Verifier to validate the signatures actually provide a // significantly strong proof for this header's validity. func (sh SignedHeader) ValidateBasic(chainID string) error { if sh.Header == nil { return errors.New("missing header") } if sh.Commit == nil { return errors.New("missing commit") } if err := sh.Header.ValidateBasic(); err != nil { return fmt.Errorf("invalid header: %w", err) } if err := sh.Commit.ValidateBasic(); err != nil { return fmt.Errorf("invalid commit: %w", err) } if sh.ChainID != chainID { return fmt.Errorf("header belongs to another chain %q, not %q", sh.ChainID, chainID) } // Make sure the header is consistent with the commit. if sh.Commit.Height != sh.Height { return fmt.Errorf("header and commit height mismatch: %d vs %d", sh.Height, sh.Commit.Height) } if hhash, chash := sh.Header.Hash(), sh.Commit.BlockID.Hash; !bytes.Equal(hhash, chash) { return fmt.Errorf("commit signs block %X, header is block %X", chash, hhash) } return nil } // String returns a string representation of SignedHeader. func (sh SignedHeader) String() string { return sh.StringIndented("") } // StringIndented returns an indented string representation of SignedHeader. // // Header // Commit func (sh SignedHeader) StringIndented(indent string) string { return fmt.Sprintf(`SignedHeader{ %s %v %s %v %s}`, indent, sh.Header.StringIndented(indent+" "), indent, sh.Commit.StringIndented(indent+" "), indent) } // ToProto converts SignedHeader to protobuf func (sh *SignedHeader) ToProto() *tmproto.SignedHeader { if sh == nil { return nil } psh := new(tmproto.SignedHeader) if sh.Header != nil { psh.Header = sh.Header.ToProto() } if sh.Commit != nil { psh.Commit = sh.Commit.ToProto() } return psh } // FromProto sets a protobuf SignedHeader to the given pointer. // It returns an error if the header or the commit is invalid. func SignedHeaderFromProto(shp *tmproto.SignedHeader) (*SignedHeader, error) { if shp == nil { return nil, errors.New("nil SignedHeader") } sh := new(SignedHeader) if shp.Header != nil { h, err := HeaderFromProto(shp.Header) if err != nil { return nil, err } sh.Header = &h } if shp.Commit != nil { c, err := CommitFromProto(shp.Commit) if err != nil { return nil, err } sh.Commit = c } return sh, nil }