Search Apps Documentation Source Content File Folder Download Copy Actions Download

header.gno

14.55 Kb · 476 lines
  1package tendermint
  2
  3import (
  4	"bytes"
  5	"encoding/binary"
  6	"encoding/hex"
  7	"errors"
  8	"time"
  9
 10	"gno.land/p/aib/encoding/proto"
 11	"gno.land/p/aib/ibc/lightclient"
 12	"gno.land/p/aib/ibc/types"
 13	"gno.land/p/aib/merkle"
 14	"gno.land/p/nt/ufmt/v0"
 15)
 16
 17// MsgHeader defines the Tendermint client consensus Header.
 18// It encapsulates all the information necessary to update from a trusted
 19// Tendermint ConsensusState. The inclusion of TrustedHeight and
 20// TrustedValidators allows this update to process correctly, so long as the
 21// ConsensusState for the TrustedHeight exists, this removes race conditions
 22// among relayers The SignedHeader and ValidatorSet are the new untrusted update
 23// fields for the client. The TrustedHeight is the height of a stored
 24// ConsensusState on the client that will be used to verify the new untrusted
 25// header. The Trusted ConsensusState must be within the unbonding period of
 26// current time in order to correctly verify, and the TrustedValidators must
 27// hash to TrustedConsensusState.NextValidatorsHash since that is the last
 28// trusted validator set at the TrustedHeight.
 29type MsgHeader struct {
 30	Header            *Header
 31	Commit            *Commit
 32	ValidatorSet      *ValidatorSet
 33	TrustedHeight     types.Height
 34	TrustedValidators *ValidatorSet
 35}
 36
 37// Implements lightclient.ClientMessage
 38func (*MsgHeader) ClientType() string {
 39	return lightclient.Tendermint
 40}
 41
 42// Implements lightclient.ClientMessage
 43// NOTE: TrustedHeight and TrustedValidators may be empty when creating client
 44// with MsgCreateClient
 45func (h *MsgHeader) ValidateBasic() error {
 46	if h.Header == nil {
 47		return errors.New("missing header")
 48	}
 49	if h.Commit == nil {
 50		return errors.New("missing commit")
 51	}
 52	if err := h.Header.ValidateBasic(); err != nil {
 53		return ufmt.Errorf("invalid header: %v", err)
 54	}
 55	if err := h.Commit.ValidateBasic(); err != nil {
 56		return ufmt.Errorf("invalid commit: %v", err)
 57	}
 58	// Make sure the header is consistent with the commit.
 59	if h.Header.Height != h.Commit.Height {
 60		return ufmt.Errorf("header and commit height mismatch: %d vs %d", h.Header.Height, h.Commit.Height)
 61	}
 62	if hhash, chash := h.Header.Hash(), h.Commit.BlockID.Hash; !bytes.Equal(hhash, chash) {
 63		cs, hs := hex.EncodeToString(chash), hex.EncodeToString(hhash)
 64		return ufmt.Errorf("expected BlockID#Hash and Header#Hash to be the same, got %q != %q", cs, hs)
 65	}
 66
 67	// TrustedHeight is less than Header for updates and misbehaviour
 68	if h.TrustedHeight.GTE(h.GetHeight()) {
 69		return ufmt.Errorf("trustedHeight %s must be less than header height %s",
 70			h.TrustedHeight, h.GetHeight())
 71	}
 72
 73	if h.ValidatorSet == nil {
 74		return ufmt.Errorf("validator set is nil")
 75	}
 76	if h.TrustedValidators == nil {
 77		return ufmt.Errorf("trusted validator set is nil")
 78	}
 79	if !bytes.Equal(h.Header.ValidatorsHash, h.ValidatorSet.Hash()) {
 80		return ufmt.Errorf("validator set does not match hash")
 81	}
 82	if len(h.ValidatorSet.Validators) != len(h.Commit.Signatures) {
 83		return ufmt.Errorf("Invalid commit -- wrong set size: %v vs %v",
 84			len(h.ValidatorSet.Validators), len(h.Commit.Signatures))
 85	}
 86	return nil
 87}
 88
 89// ConsensusState returns the updated consensus state associated with the
 90// header
 91func (h MsgHeader) ConsensusState() ConsensusState {
 92	return ConsensusState{
 93		Timestamp:          h.Header.Time,
 94		Root:               NewMerkleRoot(h.Header.AppHash),
 95		NextValidatorsHash: h.Header.NextValidatorsHash,
 96	}
 97}
 98
 99// GetHeight returns the current height. It returns 0 if the tendermint
100// header is nil.
101// NOTE: the header.Header is checked to be non nil in ValidateBasic.
102func (h MsgHeader) GetHeight() types.Height {
103	revision := types.ParseChainID(h.Header.ChainID)
104	return types.NewHeight(revision, h.Header.Height)
105}
106
107// Header defines the structure of a block header.
108type Header struct {
109	// basic block info
110	Version Consensus
111	ChainID string
112	Height  uint64
113	Time    time.Time
114
115	// prev block info
116	LastBlockID BlockID
117
118	// hashes of block data
119	// commit info from validators from the last block
120	LastCommitHash []byte
121	// transactions
122	DataHash []byte
123
124	// hashes from the app output form the preb block
125	// validators for the current block
126	ValidatorsHash []byte
127	// validators for the next block
128	NextValidatorsHash []byte
129	// consensus params for the current block
130	ConsensusHash []byte
131	// state afterx txs from the previous block
132	AppHash []byte
133	// root hash of all results from the txs from the previous block
134	LastResultsHash []byte
135
136	// consensus info
137	// evidence included in the block
138	EvidenceHash []byte
139	//original proposer of the block
140	ProposerAddress []byte
141}
142
143const (
144	// BlockProtocol versions all block data structures and processing.
145	// This includes validity of blocks and state updates.
146	BlockProtocol uint64 = 11
147	AddressSize          = 20
148	// MaxSignatureSize is a maximum allowed signature size for the Proposal
149	// and Vote.
150	// XXX: secp256k1 does not have Size nor MaxSize defined.
151	MaxSignatureSize = 64
152)
153
154// ValidateBasic performs stateless validation on a Header returning an error if any
155// validation fails.
156//
157// NOTE: Timestamp validation is subtle and handled elsewhere.
158func (h Header) ValidateBasic() error {
159	if h.Version.Block != BlockProtocol {
160		return ufmt.Errorf("block protocol is incorrect: got: %d, want: %d ", h.Version.Block, BlockProtocol)
161	}
162	if len(h.ChainID) > MaxChainIDLen {
163		return ufmt.Errorf("chainID is too long; got: %d, max: %d", len(h.ChainID), MaxChainIDLen)
164	}
165
166	if h.Height == 0 {
167		return errors.New("zero Height")
168	}
169
170	if err := h.LastBlockID.ValidateBasic(); err != nil {
171		return ufmt.Errorf("wrong LastBlockID: %v", err)
172	}
173
174	if err := validateHash(h.LastCommitHash); err != nil {
175		return ufmt.Errorf("wrong LastCommitHash: %v", err)
176	}
177
178	if err := validateHash(h.DataHash); err != nil {
179		return ufmt.Errorf("wrong DataHash: %v", err)
180	}
181
182	if err := validateHash(h.EvidenceHash); err != nil {
183		return ufmt.Errorf("wrong EvidenceHash: %v", err)
184	}
185
186	if len(h.ProposerAddress) != AddressSize {
187		return ufmt.Errorf(
188			"invalid ProposerAddress length; got: %d, expected: %d",
189			len(h.ProposerAddress), AddressSize,
190		)
191	}
192
193	// Basic validation of hashes related to application data.
194	// Will validate fully against state in state#ValidateBlock.
195	if err := validateHash(h.ValidatorsHash); err != nil {
196		return ufmt.Errorf("wrong ValidatorsHash: %v", err)
197	}
198	if err := validateHash(h.NextValidatorsHash); err != nil {
199		return ufmt.Errorf("wrong NextValidatorsHash: %v", err)
200	}
201	if err := validateHash(h.ConsensusHash); err != nil {
202		return ufmt.Errorf("wrong ConsensusHash: %v", err)
203	}
204	// NOTE: AppHash is arbitrary length
205	if err := validateHash(h.LastResultsHash); err != nil {
206		return ufmt.Errorf("wrong LastResultsHash: %v", err)
207	}
208
209	return nil
210}
211
212func (h *Header) Hash() []byte {
213	if h == nil || len(h.ValidatorsHash) == 0 {
214		return nil
215	}
216	bz := merkle.HashFromByteSlices([][]byte{
217		h.Version.ProtoMarshal(),
218		proto.AppendLengthDelimited(nil, 1, []byte(h.ChainID)),
219		proto.AppendVarint(nil, 1, h.Height),
220		proto.TimeMarshal(h.Time),
221		h.LastBlockID.ProtoMarshal(),
222		proto.AppendLengthDelimited(nil, 1, h.LastCommitHash),
223		proto.AppendLengthDelimited(nil, 1, h.DataHash),
224		proto.AppendLengthDelimited(nil, 1, h.ValidatorsHash),
225		proto.AppendLengthDelimited(nil, 1, h.NextValidatorsHash),
226		proto.AppendLengthDelimited(nil, 1, h.ConsensusHash),
227		proto.AppendLengthDelimited(nil, 1, h.AppHash),
228		proto.AppendLengthDelimited(nil, 1, h.LastResultsHash),
229		proto.AppendLengthDelimited(nil, 1, h.EvidenceHash),
230		proto.AppendLengthDelimited(nil, 1, h.ProposerAddress),
231	})
232	// For debugging purposes:
233	// hex := hex.EncodeToString
234	// println("VERSION", hex(h.Version.ProtoMarshal()))
235	// println("CHAIN", hex(proto.AppendLengthDelimited(nil, 1, []byte(h.ChainID))))
236	// println("HEIGHT", hex(proto.AppendVarint(nil, 1, h.Height)))
237	// println("TIME", hex(proto.TimeMarshal(h.Time)))
238	// println("LAST", hex(h.LastBlockID.ProtoMarshal()))
239	// println("COMMITHASH", hex(proto.AppendLengthDelimited(nil, 1, h.LastCommitHash)))
240	// println("DATAHASH", hex(proto.AppendLengthDelimited(nil, 1, h.DataHash)))
241	// println("VALHASH", hex(proto.AppendLengthDelimited(nil, 1, h.ValidatorsHash)))
242	// println("NEXTVALHASH", hex(proto.AppendLengthDelimited(nil, 1, h.NextValidatorsHash)))
243	// println("CONSHASH", hex(proto.AppendLengthDelimited(nil, 1, h.ConsensusHash)))
244	// println("APPHASH", hex(proto.AppendLengthDelimited(nil, 1, h.AppHash)))
245	// println("RES", hex(proto.AppendLengthDelimited(nil, 1, h.LastResultsHash)))
246	// println("EV", hex(proto.AppendLengthDelimited(nil, 1, h.EvidenceHash)))
247	// println("PROP", hex(proto.AppendLengthDelimited(nil, 1, h.ProposerAddress)))
248	// println("MERKLE", hex(bz))
249	return bz
250}
251
252func (h Header) Expired(trustingPeriod time.Duration) error {
253	var (
254		expirationTime = h.Time.Add(trustingPeriod)
255		now            = time.Now()
256	)
257	if !expirationTime.After(now) {
258		return ufmt.Errorf("header %d has expired at %v (now: %v)", h.Height, expirationTime, now)
259	}
260	return nil
261}
262
263// Commit contains the evidence that a block was committed by a set of validators.
264type Commit struct {
265	Height     uint64
266	Round      int32
267	BlockID    BlockID
268	Signatures []CommitSig
269}
270
271// BytesToSign returns the bytes-to-sign for a validator's vote.
272// These bytes are produced from a protobuf marshaled type [CanonicalVote][1].
273// Since proto is not available in Gno, the function tries to generate these
274// bytes manually, using the same logic as protobuf.
275//
276// [1]: https://buf.build/tendermint/tendermint/docs/main:tendermint.types#tendermint.types.CanonicalVote
277func (c *Commit) BytesToSign(chainID string, valIdx int) []byte {
278	var buf []byte
279
280	// Field 1: type (SignedMsgType - varint)
281	buf = proto.AppendVarint(buf, 1, 2) // type precommit
282
283	// Field 2: height (sfixed64 - fixed64)
284	buf = proto.AppendFixed64(buf, 2, uint64(c.Height))
285
286	// Field 3: round (sfixed64 - fixed64)
287	buf = proto.AppendFixed64(buf, 3, uint64(c.Round))
288
289	// Field 4: block_id (CanonicalBlockID - length-delimited)
290	if c.Signatures[valIdx].BlockIDFlag == BlockIDFlagCommit {
291		buf = proto.AppendLengthDelimited(buf, 4, c.BlockID.ProtoMarshal())
292	}
293
294	// Field 5: timestamp (google.protobuf.Timestamp - length-delimited)
295	buf = proto.AppendTime(buf, 5, c.Signatures[valIdx].Timestamp)
296
297	// Field 6: chain_id (string - length-delimited)
298	buf = proto.AppendLengthDelimited(buf, 6, []byte(chainID))
299
300	// Prefix with total length
301	length := len(buf)
302	buf = append(binary.AppendUvarint(nil, uint64(length)), buf...)
303
304	return buf
305}
306
307// ValidateBasic performs basic validation that doesn't involve state data.
308// Does not actually check the cryptographic signatures.
309func (c *Commit) ValidateBasic() error {
310	if c.Round < 0 {
311		return errors.New("negative Round")
312	}
313
314	if c.Height >= 1 {
315		if c.BlockID.IsZero() {
316			return errors.New("commit cannot be for nil block")
317		}
318
319		if len(c.Signatures) == 0 {
320			return errors.New("no signatures in commit")
321		}
322		for i, commitSig := range c.Signatures {
323			if err := commitSig.ValidateBasic(); err != nil {
324				return ufmt.Errorf("wrong CommitSig #%d: %v", i, err)
325			}
326		}
327	}
328	return nil
329}
330
331// CommitSig is a part of the Vote included in a Commit.
332type CommitSig struct {
333	BlockIDFlag      BlockIDFlag
334	ValidatorAddress []byte
335	Timestamp        time.Time
336	Signature        []byte
337}
338
339// ValidateBasic performs basic validation.
340func (cs CommitSig) ValidateBasic() error {
341	switch cs.BlockIDFlag {
342	case BlockIDFlagAbsent:
343	case BlockIDFlagCommit:
344	case BlockIDFlagNil:
345	default:
346		return ufmt.Errorf("unknown BlockIDFlag: %v", cs.BlockIDFlag)
347	}
348
349	switch cs.BlockIDFlag {
350	case BlockIDFlagAbsent:
351		if len(cs.ValidatorAddress) != 0 {
352			return errors.New("validator address is present")
353		}
354		if !cs.Timestamp.IsZero() {
355			return errors.New("time is present")
356		}
357		if len(cs.Signature) != 0 {
358			return errors.New("signature is present")
359		}
360	default:
361		if len(cs.ValidatorAddress) != AddressSize {
362			return ufmt.Errorf("expected ValidatorAddress size to be %d bytes, got %d bytes",
363				AddressSize,
364				len(cs.ValidatorAddress),
365			)
366		}
367		// NOTE: Timestamp validation is subtle and handled elsewhere.
368		if len(cs.Signature) == 0 {
369			return errors.New("signature is missing")
370		}
371		if len(cs.Signature) > MaxSignatureSize {
372			return ufmt.Errorf("signature is too big (max: %d)", MaxSignatureSize)
373		}
374	}
375
376	return nil
377}
378
379// BlockIdFlag indicates which BlockID the signature is for
380type BlockIDFlag byte
381
382const (
383	BlockIDFlagUnknown BlockIDFlag = 0
384	BlockIDFlagAbsent  BlockIDFlag = 1
385	BlockIDFlagCommit  BlockIDFlag = 2
386	BlockIDFlagNil     BlockIDFlag = 3
387)
388
389// BlockID
390type BlockID struct {
391	Hash          []byte
392	PartSetHeader PartSetHeader
393}
394
395// Equals returns true if the BlockID matches the given BlockID
396func (blockID BlockID) Equals(other BlockID) bool {
397	return bytes.Equal(blockID.Hash, other.Hash) &&
398		blockID.PartSetHeader.Equals(other.PartSetHeader)
399}
400
401// IsZero returns true if this is the BlockID of a nil block.
402func (blockID BlockID) IsZero() bool {
403	return len(blockID.Hash) == 0 &&
404		blockID.PartSetHeader.IsZero()
405}
406
407// ValidateBasic performs basic validation.
408func (blockID BlockID) ValidateBasic() error {
409	// Hash can be empty in case of POLBlockID in Proposal.
410	if err := validateHash(blockID.Hash); err != nil {
411		return ufmt.Errorf("wrong blockID Hash: %v", err)
412	}
413	if err := blockID.PartSetHeader.ValidateBasic(); err != nil {
414		return ufmt.Errorf("wrong PartSetHeader: %v", err)
415	}
416	return nil
417}
418
419func (b BlockID) ProtoMarshal() (bz []byte) {
420	if len(b.Hash) > 0 {
421		bz = proto.AppendLengthDelimited(bz, 1, b.Hash)
422	}
423	bz = proto.AppendLengthDelimited(bz, 2, b.PartSetHeader.ProtoMarshal())
424	return
425}
426
427// PartsetHeader
428type PartSetHeader struct {
429	Total uint32
430	Hash  []byte
431}
432
433func (psh PartSetHeader) IsZero() bool {
434	return psh.Total == 0 && len(psh.Hash) == 0
435}
436
437func (psh PartSetHeader) Equals(other PartSetHeader) bool {
438	return psh.Total == other.Total && bytes.Equal(psh.Hash, other.Hash)
439}
440
441// ValidateBasic performs basic validation.
442func (psh PartSetHeader) ValidateBasic() error {
443	// Hash can be empty in case of POLBlockID.PartSetHeader in Proposal.
444	if err := validateHash(psh.Hash); err != nil {
445		return ufmt.Errorf("wrong parSetHeader Hash: %v", err)
446	}
447	return nil
448}
449
450func (psh PartSetHeader) ProtoMarshal() (bz []byte) {
451	if psh.Total > 0 {
452		bz = proto.AppendVarint(bz, 1, uint64(psh.Total))
453	}
454	if len(psh.Hash) > 0 {
455		bz = proto.AppendLengthDelimited(bz, 2, psh.Hash)
456	}
457	return
458}
459
460// Consensus captures the consensus rules for processing a block in the blockchain,
461// including all blockchain data structures and the rules of the application's
462// state transition machine.
463type Consensus struct {
464	Block uint64
465	App   uint64
466}
467
468func (c Consensus) ProtoMarshal() (bz []byte) {
469	if c.Block != 0 {
470		bz = proto.AppendVarint(bz, 1, c.Block)
471	}
472	if c.App != 0 {
473		bz = proto.AppendVarint(bz, 2, c.App)
474	}
475	return
476}