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}