-
Notifications
You must be signed in to change notification settings - Fork 917
/
header.go
282 lines (243 loc) · 8.18 KB
/
header.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
package header
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"time"
tmjson "github.com/tendermint/tendermint/libs/json"
"github.com/tendermint/tendermint/light"
core "github.com/tendermint/tendermint/types"
"github.com/celestiaorg/celestia-app/v2/pkg/appconsts"
"github.com/celestiaorg/celestia-app/v2/pkg/da"
libhead "github.com/celestiaorg/go-header"
"github.com/celestiaorg/rsmt2d"
)
// ConstructFn aliases a function that creates an ExtendedHeader.
type ConstructFn = func(
*core.Header,
*core.Commit,
*core.ValidatorSet,
*rsmt2d.ExtendedDataSquare,
) (*ExtendedHeader, error)
// RawHeader is an alias to core.Header. It is
// "raw" because it is not yet wrapped to include
// the DataAvailabilityHeader.
type RawHeader = core.Header
// ExtendedHeader represents a wrapped "raw" header that includes
// information necessary for Celestia Nodes to be notified of new
// block headers and perform Data Availability Sampling.
type ExtendedHeader struct {
RawHeader `json:"header"`
Commit *core.Commit `json:"commit"`
ValidatorSet *core.ValidatorSet `json:"validator_set"`
DAH *da.DataAvailabilityHeader `json:"dah"`
}
// MakeExtendedHeader assembles new ExtendedHeader.
func MakeExtendedHeader(
h *core.Header,
comm *core.Commit,
vals *core.ValidatorSet,
eds *rsmt2d.ExtendedDataSquare,
) (*ExtendedHeader, error) {
var (
dah da.DataAvailabilityHeader
err error
)
switch eds {
case nil:
dah = da.MinDataAvailabilityHeader()
default:
dah, err = da.NewDataAvailabilityHeader(eds)
if err != nil {
return nil, err
}
}
eh := &ExtendedHeader{
RawHeader: *h,
DAH: &dah,
Commit: comm,
ValidatorSet: vals,
}
return eh, nil
}
func (eh *ExtendedHeader) New() *ExtendedHeader {
return new(ExtendedHeader)
}
func (eh *ExtendedHeader) IsZero() bool {
return eh == nil
}
func (eh *ExtendedHeader) ChainID() string {
return eh.RawHeader.ChainID
}
func (eh *ExtendedHeader) Height() uint64 {
return uint64(eh.RawHeader.Height)
}
func (eh *ExtendedHeader) Time() time.Time {
return eh.RawHeader.Time
}
// Hash returns Hash of the wrapped RawHeader.
// NOTE: It purposely overrides Hash method of RawHeader to get it directly from Commit without
// recomputing.
func (eh *ExtendedHeader) Hash() libhead.Hash {
return eh.Commit.BlockID.Hash.Bytes()
}
// LastHeader returns the Hash of the last wrapped RawHeader.
func (eh *ExtendedHeader) LastHeader() libhead.Hash {
return libhead.Hash(eh.RawHeader.LastBlockID.Hash)
}
// Equals returns whether the hash and height of the given header match.
func (eh *ExtendedHeader) Equals(header *ExtendedHeader) bool {
return eh.Height() == header.Height() && bytes.Equal(eh.Hash(), header.Hash())
}
// Validate performs *basic* validation to check for missed/incorrect fields.
func (eh *ExtendedHeader) Validate() error {
err := eh.RawHeader.ValidateBasic()
if err != nil {
return fmt.Errorf("ValidateBasic error on RawHeader at height %d: %w", eh.Height(), err)
}
if eh.RawHeader.Version.App == 0 || eh.RawHeader.Version.App > appconsts.LatestVersion {
return fmt.Errorf("header received at height %d has version %d, this node supports up "+
"to version %d. Please upgrade to support new version. Note, 0 is not a valid version",
eh.RawHeader.Height, eh.RawHeader.Version.App, appconsts.LatestVersion)
}
err = eh.Commit.ValidateBasic()
if err != nil {
return fmt.Errorf("ValidateBasic error on Commit at height %d: %w", eh.Height(), err)
}
err = eh.ValidatorSet.ValidateBasic()
if err != nil {
return fmt.Errorf("ValidateBasic error on ValidatorSet at height %d: %w", eh.Height(), err)
}
// make sure the validator set is consistent with the header
if valSetHash := eh.ValidatorSet.Hash(); !bytes.Equal(eh.ValidatorsHash, valSetHash) {
return fmt.Errorf("expected validator hash of header to match validator set hash (%X != %X) at height %d",
eh.ValidatorsHash, valSetHash, eh.Height(),
)
}
// ensure data root from raw header matches computed root
if !bytes.Equal(eh.DAH.Hash(), eh.DataHash) {
return fmt.Errorf("mismatch between data hash commitment from core header and computed data root "+
"at height %d: data hash: %X, computed root: %X", eh.Height(), eh.DataHash, eh.DAH.Hash())
}
// Make sure the header is consistent with the commit.
if eh.Commit.Height != eh.RawHeader.Height {
return fmt.Errorf("header and commit height mismatch: %d vs %d", eh.RawHeader.Height, eh.Commit.Height)
}
if hhash, chash := eh.RawHeader.Hash(), eh.Commit.BlockID.Hash; !bytes.Equal(hhash, chash) {
return fmt.Errorf("commit signs block %X, header is block %X", chash, hhash)
}
err = eh.ValidatorSet.VerifyCommitLight(eh.ChainID(), eh.Commit.BlockID, int64(eh.Height()), eh.Commit)
if err != nil {
return fmt.Errorf("VerifyCommitLight error at height %d: %w", eh.Height(), err)
}
err = eh.DAH.ValidateBasic()
if err != nil {
return fmt.Errorf("ValidateBasic error on DAH at height %d: %w", eh.RawHeader.Height, err)
}
return nil
}
var (
ErrValidatorHashMismatch = errors.New("validator hash mismatch")
ErrLastHeaderHashMismatch = errors.New("last header hash mismatch")
ErrVerifyCommitLightTrustingFailed = errors.New("commit light trusting verification failed")
)
// Verify validates given untrusted Header against trusted ExtendedHeader.
func (eh *ExtendedHeader) Verify(untrst *ExtendedHeader) error {
isAdjacent := eh.Height()+1 == untrst.Height()
if isAdjacent {
// Optimized verification for adjacent headers
// Check the validator hashes are the same
if !bytes.Equal(untrst.ValidatorsHash, eh.NextValidatorsHash) {
return &libhead.VerifyError{
Reason: fmt.Errorf("%w: expected (%X), but got (%X)",
ErrValidatorHashMismatch,
eh.NextValidatorsHash,
untrst.ValidatorsHash,
),
}
}
if !bytes.Equal(untrst.LastHeader(), eh.Hash()) {
return &libhead.VerifyError{
Reason: fmt.Errorf("%w: expected (%X), but got (%X)",
ErrLastHeaderHashMismatch,
eh.Hash(),
untrst.LastHeader(),
),
}
}
return nil
}
if err := eh.ValidatorSet.VerifyCommitLightTrusting(eh.ChainID(), untrst.Commit, light.DefaultTrustLevel); err != nil {
return &libhead.VerifyError{
Reason: fmt.Errorf("%w: %w", ErrVerifyCommitLightTrustingFailed, err),
SoftFailure: true,
}
}
return nil
}
// MarshalBinary marshals ExtendedHeader to binary.
func (eh *ExtendedHeader) MarshalBinary() ([]byte, error) {
return MarshalExtendedHeader(eh)
}
// UnmarshalBinary unmarshals ExtendedHeader from binary.
func (eh *ExtendedHeader) UnmarshalBinary(data []byte) error {
if eh == nil {
return fmt.Errorf("header: cannot UnmarshalBinary - nil ExtendedHeader")
}
out, err := UnmarshalExtendedHeader(data)
if err != nil {
return err
}
*eh = *out
return nil
}
// MarshalJSON marshals an ExtendedHeader to JSON. The ValidatorSet is wrapped with amino encoding,
// to be able to unmarshal the crypto.PubKey type back from JSON.
func (eh *ExtendedHeader) MarshalJSON() ([]byte, error) {
type Alias ExtendedHeader
validatorSet, err := tmjson.Marshal(eh.ValidatorSet)
if err != nil {
return nil, err
}
rawHeader, err := tmjson.Marshal(eh.RawHeader)
if err != nil {
return nil, err
}
return json.Marshal(&struct {
RawHeader json.RawMessage `json:"header"`
ValidatorSet json.RawMessage `json:"validator_set"`
*Alias
}{
ValidatorSet: validatorSet,
RawHeader: rawHeader,
Alias: (*Alias)(eh),
})
}
// UnmarshalJSON unmarshals an ExtendedHeader from JSON. The ValidatorSet is wrapped with amino
// encoding, to be able to unmarshal the crypto.PubKey type back from JSON.
func (eh *ExtendedHeader) UnmarshalJSON(data []byte) error {
type Alias ExtendedHeader
aux := &struct {
RawHeader json.RawMessage `json:"header"`
ValidatorSet json.RawMessage `json:"validator_set"`
*Alias
}{
Alias: (*Alias)(eh),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
valSet := new(core.ValidatorSet)
if err := tmjson.Unmarshal(aux.ValidatorSet, valSet); err != nil {
return err
}
rawHeader := new(RawHeader)
if err := tmjson.Unmarshal(aux.RawHeader, rawHeader); err != nil {
return err
}
eh.ValidatorSet = valSet
eh.RawHeader = *rawHeader
return nil
}
var _ libhead.Header[*ExtendedHeader] = &ExtendedHeader{}