height.gno
4.82 Kb · 160 lines
1package types
2
3import (
4 "encoding/binary"
5 "errors"
6 "strconv"
7 "strings"
8)
9
10// Height is a blockchain height, optionally prefixed with a revision number as
11// per the IBC specification. A revision of zero is treated as "no revision":
12// it is omitted from the string form and ignored during comparison, mirroring
13// the Rust `Option<NonZeroU64>` revision semantics.
14type Height struct {
15 // the revision that the client is currently on
16 RevisionNumber uint64
17 // the height within the given revision
18 RevisionHeight uint64
19}
20
21// NewHeight returns a Height with no revision.
22func NewHeight(height uint64) Height {
23 return Height{RevisionNumber: 0, RevisionHeight: height}
24}
25
26// NewHeightWithRevision returns a Height carrying both a revision and a height.
27func NewHeightWithRevision(revision, height uint64) Height {
28 return Height{RevisionNumber: revision, RevisionHeight: height}
29}
30
31// IsZero reports whether the height is the zero value (no revision, zero
32// height). Union packets require a zero timeout height.
33func (h Height) IsZero() bool {
34 return h.RevisionNumber == 0 && h.RevisionHeight == 0
35}
36
37// HasRevision reports whether the height carries a non-zero revision number.
38func (h Height) HasRevision() bool {
39 return h.RevisionNumber != 0
40}
41
42// GetRevision returns the revision number, or zero when unset.
43func (h Height) GetRevision() uint64 {
44 return h.RevisionNumber
45}
46
47// GetHeight returns the height number.
48func (h Height) GetHeight() uint64 {
49 return h.RevisionHeight
50}
51
52// RevisionMatches reports whether two heights share the same revision. When
53// either side has no revision the revisions are considered compatible.
54func (h Height) RevisionMatches(other Height) bool {
55 if h.RevisionNumber == 0 || other.RevisionNumber == 0 {
56 return true
57 }
58
59 return h.RevisionNumber == other.RevisionNumber
60}
61
62// Increment returns a copy of the height advanced by one, preserving revision.
63func (h Height) Increment() Height {
64 return Height{RevisionNumber: h.RevisionNumber, RevisionHeight: h.RevisionHeight + 1}
65}
66
67// IncrementBy returns a copy of the height advanced by n, preserving revision.
68func (h Height) IncrementBy(n uint64) Height {
69 return Height{RevisionNumber: h.RevisionNumber, RevisionHeight: h.RevisionHeight + n}
70}
71
72// Cmp compares two heights, returning -1, 0, or 1. When both heights carry a
73// revision, revisions are compared first and heights break the tie; otherwise
74// only the height numbers are compared.
75func (h Height) Cmp(other Height) int {
76 if h.RevisionNumber != 0 && other.RevisionNumber != 0 {
77 switch {
78 case h.RevisionNumber < other.RevisionNumber:
79 return -1
80 case h.RevisionNumber > other.RevisionNumber:
81 return 1
82 }
83 }
84
85 switch {
86 case h.RevisionHeight < other.RevisionHeight:
87 return -1
88 case h.RevisionHeight > other.RevisionHeight:
89 return 1
90 default:
91 return 0
92 }
93}
94
95// LT reports whether h is strictly less than other.
96func (h Height) LT(other Height) bool { return h.Cmp(other) < 0 }
97
98// LTE reports whether h is less than or equal to other.
99func (h Height) LTE(other Height) bool { return h.Cmp(other) <= 0 }
100
101// GT reports whether h is strictly greater than other.
102func (h Height) GT(other Height) bool { return h.Cmp(other) > 0 }
103
104// GTE reports whether h is greater than or equal to other.
105func (h Height) GTE(other Height) bool { return h.Cmp(other) >= 0 }
106
107// EQ reports whether h equals other under height comparison semantics.
108func (h Height) EQ(other Height) bool { return h.Cmp(other) == 0 }
109
110// String renders the height as "revision-height" when a revision is set, or as
111// the bare height otherwise.
112func (h Height) String() string {
113 if h.RevisionNumber != 0 {
114 return strconv.FormatUint(h.RevisionNumber, 10) + "-" + strconv.FormatUint(h.RevisionHeight, 10)
115 }
116
117 return strconv.FormatUint(h.RevisionHeight, 10)
118}
119
120// Bytes returns a 16-byte big-endian encoding of the height (revision then
121// height), suitable as a BPTree key with correct lexicographic ordering when
122// cast to string.
123func (h Height) Bytes() []byte {
124 buf := make([]byte, 16)
125 binary.BigEndian.PutUint64(buf[:8], h.RevisionNumber)
126 binary.BigEndian.PutUint64(buf[8:], h.RevisionHeight)
127
128 return buf
129}
130
131// ParseHeight parses a height string of the form "revision-height" or a bare
132// "height". A "revision-height" form always carries its revision, even when it
133// is zero.
134func ParseHeight(s string) (Height, error) {
135 if s == "" {
136 return Height{}, errors.New("types: invalid height string")
137 }
138
139 revStr, heightStr, found := strings.Cut(s, "-")
140 if !found {
141 height, err := strconv.ParseUint(s, 10, 64)
142 if err != nil {
143 return Height{}, err
144 }
145
146 return Height{RevisionNumber: 0, RevisionHeight: height}, nil
147 }
148
149 revision, err := strconv.ParseUint(revStr, 10, 64)
150 if err != nil {
151 return Height{}, err
152 }
153
154 height, err := strconv.ParseUint(heightStr, 10, 64)
155 if err != nil {
156 return Height{}, err
157 }
158
159 return Height{RevisionNumber: revision, RevisionHeight: height}, nil
160}