forked from celestiaorg/celestia-core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
data_availability_header.go
210 lines (186 loc) · 6.35 KB
/
data_availability_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
package da
import (
"bytes"
"errors"
"fmt"
"github.com/celestiaorg/rsmt2d"
"github.com/tendermint/tendermint/crypto/merkle"
"github.com/tendermint/tendermint/crypto/tmhash"
"github.com/tendermint/tendermint/pkg/consts"
"github.com/tendermint/tendermint/pkg/wrapper"
daproto "github.com/tendermint/tendermint/proto/tendermint/da"
)
const (
maxExtendedSquareWidth = consts.MaxSquareSize * 2
minExtendedSquareWidth = consts.MinSquareSize * 2
)
// DataAvailabilityHeader (DAHeader) contains the row and column roots of the erasure
// coded version of the data in Block.Data.
// Therefor the original Block.Data is arranged in a
// k × k matrix, which is then "extended" to a
// 2k × 2k matrix applying multiple times Reed-Solomon encoding.
// For details see Section 5.2: https://arxiv.org/abs/1809.09044
// or the Celestia specification:
// https://github.com/celestiaorg/celestia-specs/blob/master/src/specs/data_structures.md#availabledataheader
type DataAvailabilityHeader struct {
// RowRoot_j = root((M_{j,1} || M_{j,2} || ... || M_{j,2k} ))
RowsRoots [][]byte `json:"row_roots"`
// ColumnRoot_j = root((M_{1,j} || M_{2,j} || ... || M_{2k,j} ))
ColumnRoots [][]byte `json:"column_roots"`
// cached result of Hash() not to be recomputed
hash []byte
}
// NewDataAvailabilityHeader generates a DataAvailability header using the provided square size and shares
func NewDataAvailabilityHeader(eds *rsmt2d.ExtendedDataSquare) DataAvailabilityHeader {
// generate the row and col roots using the EDS
dah := DataAvailabilityHeader{
RowsRoots: eds.RowRoots(),
ColumnRoots: eds.ColRoots(),
}
// generate the hash of the data using the new roots
dah.Hash()
return dah
}
func ExtendShares(squareSize uint64, shares [][]byte) (*rsmt2d.ExtendedDataSquare, error) {
// Check that square size is with range
if squareSize < consts.MinSquareSize || squareSize > consts.MaxSquareSize {
return nil, fmt.Errorf(
"invalid square size: min %d max %d provided %d",
consts.MinSquareSize,
consts.MaxSquareSize,
squareSize,
)
}
// check that valid number of shares have been provided
if squareSize*squareSize != uint64(len(shares)) {
return nil, fmt.Errorf(
"must provide valid number of shares for square size: got %d wanted %d",
len(shares),
squareSize*squareSize,
)
}
tree := wrapper.NewErasuredNamespacedMerkleTree(squareSize)
return rsmt2d.ComputeExtendedDataSquare(shares, consts.DefaultCodec(), tree.Constructor)
}
// String returns hex representation of merkle hash of the DAHeader.
func (dah *DataAvailabilityHeader) String() string {
if dah == nil {
return "<nil DAHeader>"
}
return fmt.Sprintf("%X", dah.Hash())
}
// Equals checks equality of two DAHeaders.
func (dah *DataAvailabilityHeader) Equals(to *DataAvailabilityHeader) bool {
return bytes.Equal(dah.Hash(), to.Hash())
}
// Hash computes and caches the merkle root of the row and column roots.
func (dah *DataAvailabilityHeader) Hash() []byte {
if dah == nil {
return merkle.HashFromByteSlices(nil)
}
if len(dah.hash) != 0 {
return dah.hash
}
colsCount := len(dah.ColumnRoots)
rowsCount := len(dah.RowsRoots)
slices := make([][]byte, colsCount+rowsCount)
for i, rowRoot := range dah.RowsRoots {
slices[i] = rowRoot
}
for i, colRoot := range dah.ColumnRoots {
slices[i+colsCount] = colRoot
}
// The single data root is computed using a simple binary merkle tree.
// Effectively being root(rowRoots || columnRoots):
dah.hash = merkle.HashFromByteSlices(slices)
return dah.hash
}
func (dah *DataAvailabilityHeader) ToProto() (*daproto.DataAvailabilityHeader, error) {
if dah == nil {
return nil, errors.New("nil DataAvailabilityHeader")
}
dahp := new(daproto.DataAvailabilityHeader)
dahp.RowRoots = dah.RowsRoots
dahp.ColumnRoots = dah.ColumnRoots
return dahp, nil
}
func DataAvailabilityHeaderFromProto(dahp *daproto.DataAvailabilityHeader) (dah *DataAvailabilityHeader, err error) {
if dahp == nil {
return nil, errors.New("nil DataAvailabilityHeader")
}
dah = new(DataAvailabilityHeader)
dah.RowsRoots = dahp.RowRoots
dah.ColumnRoots = dahp.ColumnRoots
return dah, dah.ValidateBasic()
}
// ValidateBasic runs stateless checks on the DataAvailabilityHeader.
func (dah *DataAvailabilityHeader) ValidateBasic() error {
if dah == nil {
return errors.New("nil data availability header is not valid")
}
if len(dah.ColumnRoots) < minExtendedSquareWidth || len(dah.RowsRoots) < minExtendedSquareWidth {
return fmt.Errorf(
"minimum valid DataAvailabilityHeader has at least %d row and column roots",
minExtendedSquareWidth,
)
}
if len(dah.ColumnRoots) > maxExtendedSquareWidth || len(dah.RowsRoots) > maxExtendedSquareWidth {
return fmt.Errorf(
"maximum valid DataAvailabilityHeader has at most %d row and column roots",
maxExtendedSquareWidth,
)
}
if len(dah.ColumnRoots) != len(dah.RowsRoots) {
return fmt.Errorf(
"unequal number of row and column roots: row %d col %d",
len(dah.RowsRoots),
len(dah.ColumnRoots),
)
}
if err := validateHash(dah.hash); err != nil {
return fmt.Errorf("wrong hash: %v", err)
}
return nil
}
func (dah *DataAvailabilityHeader) IsZero() bool {
if dah == nil {
return true
}
return len(dah.ColumnRoots) == 0 || len(dah.RowsRoots) == 0
}
// tail is filler for all tail padded shares
// it is allocated once and used everywhere
var tailPaddingShare = append(
append(make([]byte, 0, consts.ShareSize), consts.TailPaddingNamespaceID...),
bytes.Repeat([]byte{0}, consts.ShareSize-consts.NamespaceSize)...,
)
// MinDataAvailabilityHeader returns the minimum valid data availability header.
// It is equal to the data availability header for an empty block
func MinDataAvailabilityHeader() DataAvailabilityHeader {
shares := GenerateEmptyShares(consts.MinSharecount)
eds, err := ExtendShares(consts.MinSquareSize, shares)
if err != nil {
panic(err)
}
dah := NewDataAvailabilityHeader(eds)
return dah
}
// GenerateEmptyShares generate an array of empty shares
func GenerateEmptyShares(size int) [][]byte {
shares := make([][]byte, size)
for i := 0; i < size; i++ {
shares[i] = tailPaddingShare
}
return shares
}
// validateHash returns an error if the hash is not empty, but its
// size != tmhash.Size. copy pasted from `types` package as to not import
func validateHash(h []byte) error {
if len(h) > 0 && len(h) != tmhash.Size {
return fmt.Errorf("expected size to be %d bytes, got %d bytes",
tmhash.Size,
len(h),
)
}
return nil
}