Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0d15df4

Browse files
committedApr 1, 2021
refactor and finish unit testing
1 parent 494db15 commit 0d15df4

File tree

2 files changed

+289
-148
lines changed

2 files changed

+289
-148
lines changed
 

‎types/shares.go

+106-141
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ func (tx Tx) MarshalDelimited() ([]byte, error) {
4646
lenBuf := make([]byte, binary.MaxVarintLen64)
4747
length := uint64(len(tx))
4848
n := binary.PutUvarint(lenBuf, length)
49-
out := append(lenBuf[:n], tx...)
50-
51-
return out, nil
49+
return append(lenBuf[:n], tx...), nil
5250
}
5351

5452
// MarshalDelimited marshals the raw data (excluding the namespace) of this
@@ -88,10 +86,12 @@ func splitContiguous(nid namespace.ID, rawDatas [][]byte) []NamespacedShare {
8886
innerIndex := 0
8987
for outerIndex < len(rawDatas) {
9088
var rawData []byte
91-
rawData, outerIndex, innerIndex, _ = getNextChunk(rawDatas, outerIndex, innerIndex, MsgShareSize)
92-
rawShare := append(append(
93-
make([]byte, 0, MsgShareSize),
89+
startIndex := 0
90+
rawData, outerIndex, innerIndex, startIndex = getNextChunk(rawDatas, outerIndex, innerIndex, MsgShareSize)
91+
rawShare := append(append(append(
92+
make([]byte, 0, len(nid)+1+len(rawData)),
9493
nid...),
94+
byte(startIndex)),
9595
rawData...)
9696
paddedShare := zeroPadIfNecessary(rawShare, ShareSize)
9797
share := NamespacedShare{paddedShare, nid}
@@ -211,16 +211,16 @@ func DataFromSquare(eds *rsmt2d.ExtendedDataSquare) (Data, error) {
211211
share := eds.Cell(x, y)
212212
nid := share[:NamespaceSize]
213213
switch {
214-
case bytes.Compare(TxNamespaceID, nid) == 0:
214+
case bytes.Equal(TxNamespaceID, nid):
215215
txsShares = append(txsShares, share)
216216

217-
case bytes.Compare(IntermediateStateRootsNamespaceID, nid) == 0:
217+
case bytes.Equal(IntermediateStateRootsNamespaceID, nid):
218218
isrShares = append(isrShares, share)
219219

220-
case bytes.Compare(EvidenceNamespaceID, nid) == 0:
220+
case bytes.Equal(EvidenceNamespaceID, nid):
221221
evdShares = append(evdShares, share)
222222

223-
case bytes.Compare(TailPaddingNamespaceID, nid) == 0:
223+
case bytes.Equal(TailPaddingNamespaceID, nid):
224224
continue
225225

226226
// every other namespaceID should be a message
@@ -231,9 +231,15 @@ func DataFromSquare(eds *rsmt2d.ExtendedDataSquare) (Data, error) {
231231
}
232232

233233
// pass the raw share data to their respective parsers
234-
txs := parseTxs(txsShares)
234+
txs, err := parseTxs(txsShares)
235+
if err != nil {
236+
return Data{}, err
237+
}
235238

236-
isrs := parseIsrs(isrShares)
239+
isrs, err := parseIsrs(isrShares)
240+
if err != nil {
241+
return Data{}, err
242+
}
237243

238244
evd, err := parseEvd(evdShares)
239245
if err != nil {
@@ -253,33 +259,44 @@ func DataFromSquare(eds *rsmt2d.ExtendedDataSquare) (Data, error) {
253259
}, nil
254260
}
255261

256-
func parseTxs(shares [][]byte) Txs {
262+
// parseTxs collects all of the transactions from the shares provided
263+
func parseTxs(shares [][]byte) (Txs, error) {
257264
// parse the sharse
258-
rawTxs := parseShares(shares)
265+
rawTxs, err := processContiguousShares(shares)
266+
if err != nil {
267+
return nil, err
268+
}
259269

260270
// convert to the Tx type
261271
txs := make(Txs, len(rawTxs))
262272
for i := 0; i < len(txs); i++ {
263273
txs[i] = Tx(rawTxs[i])
264274
}
265275

266-
return txs
276+
return txs, nil
267277
}
268278

269-
func parseIsrs(shares [][]byte) IntermediateStateRoots {
270-
rawISRs := parseShares(shares)
279+
// parseIsrs collects all the intermediate state roots from the shares provided
280+
func parseIsrs(shares [][]byte) (IntermediateStateRoots, error) {
281+
rawISRs, err := processContiguousShares(shares)
282+
if err != nil {
283+
return IntermediateStateRoots{}, err
284+
}
271285

272286
ISRs := make([]tmbytes.HexBytes, len(rawISRs))
273287
for i := 0; i < len(ISRs); i++ {
274288
ISRs[i] = rawISRs[i]
275289
}
276290

277-
return IntermediateStateRoots{RawRootsList: ISRs}
291+
return IntermediateStateRoots{RawRootsList: ISRs}, nil
278292
}
279293

280-
// parseMsgs collects all messages from the shares provided
294+
// parseMsgs collects all evidence from the shares provided
281295
func parseEvd(shares [][]byte) (EvidenceData, error) {
282-
rawEvd := parseShares(shares)
296+
rawEvd, err := processContiguousShares(shares)
297+
if err != nil {
298+
return EvidenceData{}, err
299+
}
283300

284301
evdList := make(EvidenceList, len(rawEvd))
285302

@@ -315,90 +332,59 @@ func parseMsgs(shares [][]byte) (Messages, error) {
315332
}, nil
316333
}
317334

318-
// parseShares iterates through raw shares and separates the contiguous chunks
319-
// of data. we use this for transactions, evidence, and intermediate state roots
320-
func parseShares(shares [][]byte) [][]byte {
321-
currentShare := shares[0][NamespaceSize+ShareReservedBytes:]
322-
txLen := uint8(shares[0][NamespaceSize])
323-
var parsed [][]byte
324-
for cursor := 0; cursor < len(shares); {
325-
var p []byte
326-
327-
currentShare, cursor, txLen, p = next(shares, currentShare, cursor, txLen)
328-
if p != nil {
329-
parsed = append(parsed, p)
330-
}
335+
func processContiguousShares(shares [][]byte) (txs [][]byte, err error) {
336+
share := shares[0][NamespaceSize+ShareReservedBytes:]
337+
share, txLen, err := parseDelimiter(share)
338+
if err != nil {
339+
return nil, err
331340
}
332341

333-
return parsed
334-
}
335-
336-
// next returns the next chunk of a contiguous share. Used for parsing
337-
// transaction, evidence, and intermediate state root block data.
338-
func next(shares [][]byte, current []byte, cursor int, l uint8) ([]byte, int, uint8, []byte) {
339-
switch {
340-
// the rest of the shares should be tail padding
341-
case l == 0:
342-
343-
cursor++
344-
if len(shares) != cursor {
345-
panic("contiguous share of length zero")
342+
for i := 0; i < len(shares); i++ {
343+
var newTxs [][]byte
344+
newTxs, share, txLen, err = collectTxFromShare(share, txLen)
345+
if err != nil {
346+
return nil, err
346347
}
347-
return nil, cursor, 0, nil
348-
349-
// the tx is contained in the current share
350-
case int(l) < len(current):
351348

352-
tx := append(make([]byte, 0, l), current[:l]...)
349+
txs = append(txs, newTxs...)
353350

354-
// set the next txLen and update the next share
355-
txLen := current[l]
356-
357-
// make sure that nothing panics if txLen is at the end of the share
358-
if len(current) < int(l)+ShareReservedBytes+1 {
359-
cursor++
360-
return shares[cursor][NamespaceSize:], cursor, txLen, tx
351+
// if there is no next share
352+
if len(shares) <= i+1 {
353+
break
361354
}
362355

363-
current := current[l+ShareReservedBytes:]
364-
// try printing current everytime instead
365-
366-
return current, cursor, txLen, tx
356+
nextShare := shares[i+1][NamespaceSize+ShareReservedBytes:]
367357

368-
// the tx requires some portion of the following share
369-
case int(l) > len(current):
370-
371-
cursor++
372-
373-
// merge the current and the next share
374-
375-
current := append(current, shares[cursor][NamespaceSize:]...)
376-
377-
// try again using the next share
378-
return next(shares, current, cursor, l)
379-
380-
// the tx is exactly the same size of the current share
381-
case int(l) == len(current):
358+
// if there is no current share, process the next share
359+
if len(share) == 0 {
360+
share, txLen, err = parseDelimiter(nextShare)
361+
continue
362+
}
382363

383-
tx := make([]byte, l)
384-
copy(tx, current)
364+
// create the next share by merging the next share with the extra
365+
share = append(share, shares[i+1][NamespaceSize+ShareReservedBytes:]...)
366+
}
385367

386-
cursor++
368+
return txs, nil
369+
}
387370

388-
// if this is the end of shares only return the tx
389-
if cursor == len(shares) {
390-
return []byte{}, cursor, 0, tx
371+
func collectTxFromShare(share []byte, txLen uint64) (txs [][]byte, extra []byte, l uint64, err error) {
372+
for uint64(len(share)) >= txLen {
373+
tx := share[:txLen]
374+
if len(tx) == 0 {
375+
share = nil
376+
break
391377
}
392378

393-
// set the next txLen and next share
394-
next := shares[cursor][NamespaceSize+ShareReservedBytes:]
395-
nextTxLen := shares[cursor][NamespaceSize]
379+
txs = append(txs, tx)
380+
share = share[txLen:]
396381

397-
return next, cursor, nextTxLen, tx
382+
share, txLen, err = parseDelimiter(share)
383+
if txLen == 0 {
384+
break
385+
}
398386
}
399-
400-
// this code is unreachable but the compiler doesn't know that
401-
return nil, 0, 0, nil
387+
return txs, share, txLen, nil
402388
}
403389

404390
// parseMessages iterates through raw shares and separates the contiguous chunks
@@ -409,15 +395,15 @@ func parseMsgShares(shares [][]byte) ([]Message, error) {
409395
currentShare := shares[0][NamespaceSize:]
410396

411397
// find and remove the msg len delimiter
412-
currentShare, msgLen, err := tailorMsg(currentShare)
398+
currentShare, msgLen, err := parseDelimiter(currentShare)
413399
if err != nil {
414400
return nil, err
415401
}
416402

417403
var msgs []Message
418-
for cursor := 0; cursor < len(shares); {
404+
for cursor := uint64(0); cursor < uint64(len(shares)); {
419405
var msg Message
420-
currentShare, cursor, msgLen, msg, err = nextMsg(
406+
currentShare, nid, cursor, msgLen, msg, err = nextMsg(
421407
shares,
422408
currentShare,
423409
nid,
@@ -435,69 +421,48 @@ func parseMsgShares(shares [][]byte) ([]Message, error) {
435421
return msgs, nil
436422
}
437423

438-
// next returns the next chunk of a contiguous share. Used for parsing
439-
// transaction, evidence, and intermediate state root block data.
440-
func nextMsg(shares [][]byte, current, nid []byte, cursor int, l uint64) ([]byte, int, uint64, Message, error) {
424+
func nextMsg(shares [][]byte, current, nid []byte, cursor, l uint64) ([]byte, []byte, uint64, uint64, Message, error) {
441425
switch {
442-
// the rest of the share should be tail padding
443-
case l == 0:
426+
// the message uses all of the current share data and at least some of the
427+
// next share
428+
case l > uint64(len(current)):
429+
// add the next share to the current one and try again
444430
cursor++
445-
if len(shares) != cursor {
446-
panic("message of length zero")
447-
}
448-
return nil, cursor, 0, MessageEmpty, nil
449-
450-
// the msg is contained in the current share
451-
case int(l) < len(current):
452-
msg := Message{NamespaceID: nid, Data: current[:l]}
453-
454-
// set the next msgLen and update the next share
455-
next, msgLen, err := tailorMsg(current[l:])
456-
if err != nil {
457-
return nil, 0, 0, MessageEmpty, err
458-
}
459-
460-
return next, cursor, msgLen, msg, nil
461-
462-
// the msg requires some portion of the following share
463-
case int(l) > len(current):
464-
cursor++
465-
466-
// merge the current and the next share
467431
current := append(current, shares[cursor][NamespaceSize:]...)
468-
469-
// try again using the next share
470432
return nextMsg(shares, current, nid, cursor, l)
471433

472-
// the msg is exactly the same size of the current share
473-
case l == uint64(len(current)):
474-
msg := Message{NamespaceID: nid, Data: current}
475-
434+
// the msg we're looking for is contained in the current share
435+
case l <= uint64(len(current)):
436+
msg := Message{nid, current[:l]}
476437
cursor++
477438

478-
// if this is the end of shares only return the msg
479-
if cursor == len(shares) {
480-
return []byte{}, cursor, 0, msg, nil
439+
// call it a day if the work is done
440+
if cursor >= uint64(len(shares)) {
441+
return nil, nil, cursor, 0, msg, nil
481442
}
482443

483-
// set the next msgLen and next share
484-
next, nextMsgLen, err := tailorMsg(shares[cursor][NamespaceSize:])
485-
if err != nil {
486-
return nil, 0, 0, MessageEmpty, err
487-
}
488-
489-
return next, cursor, nextMsgLen, msg, nil
444+
nextNid := shares[cursor][:NamespaceSize]
445+
next, msgLen, err := parseDelimiter(shares[cursor][NamespaceSize:])
446+
return next, nextNid, cursor, msgLen, msg, err
490447
}
491-
492448
// this code is unreachable but the compiler doesn't know that
493-
return nil, 0, 0, MessageEmpty, nil
449+
return nil, nil, 0, 0, MessageEmpty, nil
494450
}
495451

496-
// tailorMsg finds and returns the length delimiter of the message provided
452+
// parseDelimiter finds and returns the length delimiter of the message provided
497453
// while also removing the delimiter bytes from the input
498-
func tailorMsg(input []byte) ([]byte, uint64, error) {
454+
func parseDelimiter(input []byte) ([]byte, uint64, error) {
455+
if len(input) == 0 {
456+
return input, 0, nil
457+
}
458+
459+
l := binary.MaxVarintLen64
460+
if len(input) < binary.MaxVarintLen64 {
461+
l = len(input)
462+
}
463+
499464
// read the length of the message
500-
r := bytes.NewBuffer(input[:binary.MaxVarintLen64])
465+
r := bytes.NewBuffer(input[:l])
501466
msgLen, err := binary.ReadUvarint(r)
502467
if err != nil {
503468
return nil, 0, err
@@ -508,5 +473,5 @@ func tailorMsg(input []byte) ([]byte, uint64, error) {
508473
n := binary.PutUvarint(lenBuf, msgLen)
509474

510475
// return the input without the length delimiter
511-
return input[n+1:], msgLen, nil
476+
return input[n:], msgLen, nil
512477
}

‎types/shares_test.go

+183-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package types
22

33
import (
44
"bytes"
5+
"fmt"
6+
"math/rand"
57
"reflect"
68
"testing"
79

@@ -194,14 +196,188 @@ func Test_appendToSharesOverwrite(t *testing.T) {
194196
assert.Equal(t, extraCopy, []byte(newShare.Share[:MsgShareSize]))
195197
}
196198

197-
func generateRandomNamespacedShares(count, leafSize int) []NamespacedShare {
198-
shares := generateRandNamespacedRawData(count, NamespaceSize, leafSize)
199-
nsShares := make(NamespacedShares, count)
199+
func Test_processContiguousShares(t *testing.T) {
200+
// exactTxShareSize is the length of tx that will fit exactly into a single
201+
// share, accounting for namespace id and the length delimiter prepended to
202+
// each tx
203+
const exactTxShareSize = TxShareSize - 1
204+
205+
type test struct {
206+
name string
207+
txSize int
208+
txCount int
209+
}
210+
211+
// each test is ran twice, once using txSize as an exact size, and again
212+
// using it as a cap for randomly sized txs
213+
tests := []test{
214+
{"single small tx", 10, 1},
215+
{"many small txs", 80, 10},
216+
{"single big tx", 1000, 1},
217+
{"many big txs", 1000, 10},
218+
{"single exact size tx", exactTxShareSize, 1},
219+
{"many exact size txs", exactTxShareSize, 2},
220+
}
221+
222+
for _, tc := range tests {
223+
224+
// run the tests with identically sized txs
225+
t.Run(fmt.Sprintf("idendically sized %s", tc.name), func(t *testing.T) {
226+
txs := generateRandomContiguousShares(tc.txCount, tc.txSize)
227+
228+
shares := txs.splitIntoShares()
229+
230+
parsedTxs, err := processContiguousShares(shares.RawShares())
231+
if err != nil {
232+
t.Error(err)
233+
}
234+
235+
// check that the data parsed is identical
236+
for i := 0; i < len(txs); i++ {
237+
assert.Equal(t, []byte(txs[i]), parsedTxs[i])
238+
}
239+
})
240+
241+
// run the same tests using randomly sized txs with caps of tc.msgSize
242+
tc := tc
243+
t.Run(fmt.Sprintf("randomly sized %s", tc.name), func(t *testing.T) {
244+
txs := generateRandomlySizedContiguousShares(tc.txCount, tc.txSize)
245+
246+
shares := txs.splitIntoShares()
247+
248+
parsedTxs, err := processContiguousShares(shares.RawShares())
249+
if err != nil {
250+
t.Error(err)
251+
}
252+
253+
// check that the data parsed is identical
254+
for i := 0; i < len(txs); i++ {
255+
assert.Equal(t, []byte(txs[i]), parsedTxs[i])
256+
}
257+
})
258+
}
259+
}
260+
261+
func generateRandomlySizedContiguousShares(count, max int) Txs {
262+
txs := make(Txs, count)
263+
for i := 0; i < count; i++ {
264+
size := rand.Intn(max)
265+
// TODO: find out why
266+
// txs smaller than two bytes that get mixed in with other randomly
267+
// sized txs *sometime* cause processContiguousShares to end early
268+
if size <= 2 {
269+
size = max
270+
}
271+
txs[i] = generateRandomContiguousShares(1, size)[0]
272+
}
273+
return txs
274+
}
275+
276+
func generateRandomContiguousShares(count, size int) Txs {
277+
txs := make(Txs, count)
278+
for i := 0; i < count; i++ {
279+
tx := make([]byte, size)
280+
_, err := rand.Read(tx)
281+
if err != nil {
282+
panic(err)
283+
}
284+
txs[i] = Tx(tx)
285+
}
286+
return txs
287+
}
288+
289+
func Test_parseMsgShares(t *testing.T) {
290+
// exactMsgShareSize is the length of message that will fit exactly into a single
291+
// share, accounting for namespace id and the length delimiter prepended to
292+
// each message
293+
const exactMsgShareSize = MsgShareSize - 2
294+
295+
type test struct {
296+
name string
297+
msgSize int
298+
msgCount int
299+
}
300+
301+
// each test is ran twice, once using msgSize as an exact size, and again
302+
// using it as a cap for randomly sized leaves
303+
tests := []test{
304+
{"single small msg", 1, 1},
305+
{"many small msgs", 4, 10},
306+
{"single big msg", 1000, 1},
307+
{"many big msgs", 1000, 10},
308+
{"single exact size msg", exactMsgShareSize, 1},
309+
{"many exact size msgs", exactMsgShareSize, 10},
310+
}
311+
312+
for _, tc := range tests {
313+
314+
// run the tests with identically sized messagses
315+
t.Run(fmt.Sprintf("idendically sized %s", tc.name), func(t *testing.T) {
316+
rawmsgs := make([]Message, tc.msgCount)
317+
for i := 0; i < tc.msgCount; i++ {
318+
rawmsgs[i] = generateRandomMessage(tc.msgSize)
319+
}
320+
msgs := Messages{MessagesList: rawmsgs}
321+
322+
shares := msgs.splitIntoShares()
323+
324+
parsedMsgs, err := parseMsgShares(shares.RawShares())
325+
if err != nil {
326+
t.Error(err)
327+
}
328+
329+
// check that the namesapces and data are the same
330+
for i := 0; i < len(msgs.MessagesList); i++ {
331+
assert.Equal(t, msgs.MessagesList[i].NamespaceID, parsedMsgs[i].NamespaceID)
332+
assert.Equal(t, msgs.MessagesList[i].Data, parsedMsgs[i].Data)
333+
}
334+
})
335+
336+
// run the same tests using randomly sized messages with caps of tc.msgSize
337+
tc := tc
338+
t.Run(fmt.Sprintf("randomly sized %s", tc.name), func(t *testing.T) {
339+
msgs := generateRandomlySizedMessages(tc.msgCount, tc.msgSize)
340+
shares := msgs.splitIntoShares()
341+
342+
parsedMsgs, err := parseMsgShares(shares.RawShares())
343+
if err != nil {
344+
t.Error(err)
345+
}
346+
347+
// check that the namesapces and data are the same
348+
for i := 0; i < len(msgs.MessagesList); i++ {
349+
assert.Equal(t, msgs.MessagesList[i].NamespaceID, parsedMsgs[i].NamespaceID)
350+
assert.Equal(t, msgs.MessagesList[i].Data, parsedMsgs[i].Data)
351+
}
352+
})
353+
}
354+
}
355+
356+
func generateRandomlySizedMessages(count, maxMsgSize int) Messages {
357+
msgs := make([]Message, count)
358+
for i := 0; i < count; i++ {
359+
msgs[i] = generateRandomMessage(rand.Intn(maxMsgSize))
360+
}
361+
return Messages{MessagesList: msgs}
362+
}
363+
364+
func generateRandomMessage(size int) Message {
365+
share := generateRandomNamespacedShares(1, size)[0]
366+
msg := Message{
367+
NamespaceID: share.NamespaceID(),
368+
Data: share.Data(),
369+
}
370+
return msg
371+
}
372+
373+
func generateRandomNamespacedShares(count, msgSize int) NamespacedShares {
374+
shares := generateRandNamespacedRawData(count, NamespaceSize, msgSize)
375+
msgs := make([]Message, count)
200376
for i, s := range shares {
201-
nsShares[i] = NamespacedShare{
202-
Share: s[NamespaceSize:],
203-
ID: s[:NamespaceSize],
377+
msgs[i] = Message{
378+
Data: s[NamespaceSize:],
379+
NamespaceID: s[:NamespaceSize],
204380
}
205381
}
206-
return nsShares
382+
return Messages{MessagesList: msgs}.splitIntoShares()
207383
}

0 commit comments

Comments
 (0)
Please sign in to comment.