Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

encoding.gno

4.35 Kb · 199 lines
  1package cometbls
  2
  3import (
  4	"errors"
  5
  6	"gno.land/p/aib/encoding/proto"
  7	abienc "gno.land/p/onbloc/encoding/abi"
  8	"gno.land/p/onbloc/ibc/union/types"
  9)
 10
 11// Wire formats for CometBLS client messages.
 12//
 13// Each light client owns the parsing of its own client messages, since the
 14// header layout differs per client. CometBLS uses:
 15//   - Header: Ethereum ABI tuple (mirrors Union's Solidity Header struct)
 16//   - Misbehaviour: protobuf with two embedded ABI-encoded headers
 17//
 18// Header ABI layout (Solidity params tuple form):
 19//
 20//	[0] uint64  Height
 21//	[1] uint64  TimeSeconds
 22//	[2] uint64  TimeNanos
 23//	[3] bytes32 ValidatorsHash
 24//	[4] bytes32 NextValidatorsHash
 25//	[5] bytes32 AppHash
 26//	[6] uint64  TrustedHeight
 27//	[7] bytes   ZeroKnowledgeProof (dynamic)
 28var headerSchema = abienc.Schema{Fields: []abienc.Field{
 29	{Type: abienc.TypeUint64},
 30	{Type: abienc.TypeUint64},
 31	{Type: abienc.TypeUint64},
 32	{Type: abienc.TypeBytes32},
 33	{Type: abienc.TypeBytes32},
 34	{Type: abienc.TypeBytes32},
 35	{Type: abienc.TypeUint64},
 36	{Type: abienc.TypeBytes},
 37}}
 38
 39// maxTimeNanos bounds the sub-second component of a header timestamp.
 40const maxTimeNanos = 1_000_000_000
 41
 42// EncodeHeader ABI-encodes a Header. ValidatorsHash, NextValidatorsHash and
 43// AppHash must each be exactly 32 bytes.
 44func EncodeHeader(h *Header) ([]byte, error) {
 45	if h == nil || h.SignedHeader == nil {
 46		return nil, errors.New("nil header")
 47	}
 48
 49	lh := h.SignedHeader
 50
 51	vh, err := toBytes32(lh.ValidatorsHash)
 52	if err != nil {
 53		return nil, err
 54	}
 55
 56	nvh, err := toBytes32(lh.NextValidatorsHash)
 57	if err != nil {
 58		return nil, err
 59	}
 60
 61	ah, err := toBytes32(lh.AppHash)
 62	if err != nil {
 63		return nil, err
 64	}
 65
 66	var trustedHeight uint64
 67	if h.TrustedHeight != nil {
 68		trustedHeight = h.TrustedHeight.RevisionHeight
 69	}
 70
 71	return abienc.Encode(headerSchema, []any{
 72		uint64(lh.Height),
 73		uint64(lh.TimeSeconds),
 74		uint64(lh.TimeNanos),
 75		vh, nvh, ah,
 76		trustedHeight,
 77		h.ZeroKnowledgeProof,
 78	})
 79}
 80
 81// DecodeHeader ABI-decodes a Header.
 82func DecodeHeader(buf []byte) (*Header, error) {
 83	vals, err := abienc.Decode(headerSchema, buf)
 84	if err != nil {
 85		return nil, errors.New("" + err.Error())
 86	}
 87
 88	timeSeconds := vals[1].(uint64)
 89
 90	timeNanos := vals[2].(uint64)
 91	if timeNanos >= maxTimeNanos {
 92		return nil, errors.New("TimeNanos out of range")
 93	}
 94
 95	vh := vals[3].([32]byte)
 96	nvh := vals[4].([32]byte)
 97	ah := vals[5].([32]byte)
 98
 99	trustedHeight := types.NewHeight(vals[6].(uint64))
100
101	return &Header{
102		SignedHeader: &LightHeader{
103			Height:             int64(vals[0].(uint64)),
104			TimeSeconds:        int64(timeSeconds),
105			TimeNanos:          int32(timeNanos),
106			ValidatorsHash:     vh[:],
107			NextValidatorsHash: nvh[:],
108			AppHash:            ah[:],
109		},
110		TrustedHeight:      &trustedHeight,
111		ZeroKnowledgeProof: vals[7].([]byte),
112	}, nil
113}
114
115// EncodeMisbehaviour protobuf-encodes a Misbehaviour: field 1 is Header1 and
116// field 2 is Header2, each an ABI-encoded header.
117func EncodeMisbehaviour(m *Misbehaviour) ([]byte, error) {
118	if m == nil {
119		return nil, errors.New("nil misbehaviour")
120	}
121
122	h1, err := EncodeHeader(m.Header1)
123	if err != nil {
124		return nil, err
125	}
126
127	h2, err := EncodeHeader(m.Header2)
128	if err != nil {
129		return nil, err
130	}
131
132	buf := make([]byte, 0, len(h1)+len(h2)+8)
133	buf = proto.AppendLengthDelimited(buf, 1, h1)
134	buf = proto.AppendLengthDelimited(buf, 2, h2)
135
136	return buf, nil
137}
138
139// DecodeMisbehaviour protobuf-decodes a Misbehaviour.
140func DecodeMisbehaviour(buf []byte) (*Misbehaviour, error) {
141	m := &Misbehaviour{}
142
143	pos := 0
144	for pos < len(buf) {
145		fieldNum, wireType, newPos, err := decodeTag(buf, pos)
146		if err != nil {
147			return nil, err
148		}
149
150		pos = newPos
151
152		if wireType != proto.LEN {
153			pos, err = skipField(buf, pos, wireType)
154			if err != nil {
155				return nil, err
156			}
157
158			continue
159		}
160
161		s, newPos, err := proto.DecodeString(buf, pos)
162		if err != nil {
163			return nil, err
164		}
165
166		pos = newPos
167
168		header, err := DecodeHeader([]byte(s))
169		if err != nil {
170			return nil, err
171		}
172
173		switch fieldNum {
174		case 1:
175			m.Header1 = header
176		case 2:
177			m.Header2 = header
178		}
179	}
180
181	if m.Header1 == nil || m.Header2 == nil {
182		return nil, errors.New("misbehaviour missing a header")
183	}
184
185	return m, nil
186}
187
188// toBytes32 converts a 32-byte slice into a [32]byte array, returning an error
189// for any other length.
190func toBytes32(b []byte) ([32]byte, error) {
191	var out [32]byte
192	if len(b) != 32 {
193		return out, errors.New("must be 32 bytes")
194	}
195
196	copy(out[:], b)
197
198	return out, nil
199}