Skip to content

Commit

Permalink
feat: support parsing out of context compact shares (celestiaorg#1770)
Browse files Browse the repository at this point in the history
<!--
Please read and fill out this form before submitting your PR.

Please make sure you have reviewed our contributors guide before
submitting your
first PR.
-->

## Overview

Adds logic to handle out of context compact shares and uses it in
`parseCompactShares`. Also, adds a corresponding test with different
random slices of list of shares to check they are interpretable back
into Txs.

Closes: celestiaorg#802 
<!-- 
Please provide an explanation of the PR, including the appropriate
context,
background, goal, and rationale. If there is an issue with this
information,
please provide a tl;dr and link the issue. 
-->

## Checklist

<!-- 
Please complete the checklist to ensure that the PR is ready to be
reviewed.

IMPORTANT:
PRs should be left in Draft until the below checklist is completed.
-->

- [x] New and updated code has appropriate documentation
- [x] New and updated code has new and/or updated testing
- [x] Required CI checks are passing
- [x] Visual proof for any user facing features like CLI or
documentation updates
- [x] Linked issues closed with keywords

---------

Co-authored-by: Rootul P <[email protected]>
Co-authored-by: Evan Forbes <[email protected]>
  • Loading branch information
3 people authored and rach-id committed May 23, 2023
1 parent 787d17c commit d5276b2
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 17 deletions.
46 changes: 42 additions & 4 deletions pkg/shares/compact_shares_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,48 @@ func Test_processCompactShares(t *testing.T) {
}
}

func TestAllSplit(t *testing.T) {
txs := testfactory.GenerateRandomlySizedTxs(1000, 150)
txShares, _, _, err := SplitTxs(txs)
require.NoError(t, err)
resTxs, err := ParseTxs(txShares)
require.NoError(t, err)
assert.Equal(t, resTxs, txs)
}

func TestParseRandomOutOfContextShares(t *testing.T) {
txs := testfactory.GenerateRandomlySizedTxs(1000, 150)
txShares, _, _, err := SplitTxs(txs)
require.NoError(t, err)

for i := 0; i < 1000; i++ {
start, length := testfactory.GetRandomSubSlice(len(txShares))
randomRange := NewRange(start, start+length)
resTxs, err := ParseTxs(txShares[randomRange.Start:randomRange.End])
require.NoError(t, err)
assert.True(t, testfactory.CheckSubArray(txs, resTxs))
}
}

func TestParseOutOfContextSharesUsingShareRanges(t *testing.T) {
txs := testfactory.GenerateRandomlySizedTxs(1000, 150)
txShares, _, shareRanges, err := SplitTxs(txs)
require.NoError(t, err)

for key, r := range shareRanges {
resTxs, err := ParseTxs(txShares[r.Start:r.End])
require.NoError(t, err)
has := false
for _, tx := range resTxs {
if tx.Key() == key {
has = true
break
}
}
assert.True(t, has)
}
}

func TestCompactShareContainsInfoByte(t *testing.T) {
css := NewCompactShareSplitter(appns.TxNamespace, appconsts.ShareVersionZero)
txs := testfactory.GenerateRandomTxs(1, appconsts.ContinuationCompactShareContentSize/4)
Expand Down Expand Up @@ -178,10 +220,6 @@ func Test_parseCompactSharesErrors(t *testing.T) {
}

testCases := []testCase{
{
"share with start indicator false",
txShares[1:], // set the first share to the second share which has the start indicator set to false
},
{
"share with unsupported share version",
[]Share{*shareWithUnsupportedShareVersion},
Expand Down
29 changes: 16 additions & 13 deletions pkg/shares/parse_compact_shares.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package shares

import "errors"

// parseCompactShares returns data (transactions or intermediate state roots
// based on the contents of rawShares and supportedShareVersions. If rawShares
// contains a share with a version that isn't present in supportedShareVersions,
Expand All @@ -13,14 +11,6 @@ func parseCompactShares(shares []Share, supportedShareVersions []uint8) (data []
return nil, nil
}

seqStart, err := shares[0].IsSequenceStart()
if err != nil {
return nil, err
}
if !seqStart {
return nil, errors.New("first share is not the start of a sequence")
}

err = validateShareVersions(shares, supportedShareVersions)
if err != nil {
return nil, err
Expand Down Expand Up @@ -61,19 +51,32 @@ func parseRawData(rawData []byte) (units [][]byte, err error) {
if err != nil {
return nil, err
}
// the rest of raw data is padding
if unitLen == 0 {
return units, nil
}
// the rest of actual data contains only part of the next transaction so
// we stop parsing raw data
if unitLen > uint64(len(actualData)) {
return units, nil
}
rawData = actualData[unitLen:]
units = append(units, actualData[:unitLen])
}
}

// extractRawData returns the raw data contained in the shares. The raw data does
// not contain the namespace ID, info byte, sequence length, or reserved bytes.
// extractRawData returns the raw data representing complete transactions
// contained in the shares. The raw data does not contain the namespace, info
// byte, sequence length, or reserved bytes. Starts reading raw data based on
// the reserved bytes in the first share.
func extractRawData(shares []Share) (rawData []byte, err error) {
for i := 0; i < len(shares); i++ {
raw, err := shares[i].RawData()
var raw []byte
if i == 0 {
raw, err = shares[i].RawDataUsingReserved()
} else {
raw, err = shares[i].RawData()
}
if err != nil {
return nil, err
}
Expand Down
45 changes: 45 additions & 0 deletions pkg/shares/shares.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,51 @@ func (s *Share) rawDataStartIndex() int {
return index
}

// RawDataWithReserved returns the raw share data while taking reserved bytes into account.
func (s *Share) RawDataUsingReserved() (rawData []byte, err error) {
rawDataStartIndexUsingReserved, err := s.rawDataStartIndexUsingReserved()
if err != nil {
return nil, err
}

// This means share is the last share and does not have any transaction beginning in it
if rawDataStartIndexUsingReserved == 0 {
return []byte{}, nil
}
if len(s.data) < rawDataStartIndexUsingReserved {
return rawData, fmt.Errorf("share %s is too short to contain raw data", s)
}

return s.data[rawDataStartIndexUsingReserved:], nil
}

// rawDataStartIndexUsingReserved returns the start index of raw data while accounting for
// reserved bytes, if it exists in the share.
func (s *Share) rawDataStartIndexUsingReserved() (int, error) {
isStart, err := s.IsSequenceStart()
if err != nil {
return 0, err
}
isCompact, err := s.IsCompactShare()
if err != nil {
return 0, err
}

index := appconsts.NamespaceSize + appconsts.ShareInfoBytes
if isStart {
index += appconsts.SequenceLenBytes
}

if isCompact {
reservedBytes, err := ParseReservedBytes(s.data[index : index+appconsts.CompactShareReservedBytes])
if err != nil {
return 0, err
}
return int(reservedBytes), nil
}
return index, nil
}

func ToBytes(shares []Share) (bytes [][]byte) {
bytes = make([][]byte, len(shares))
for i, share := range shares {
Expand Down
26 changes: 26 additions & 0 deletions test/util/testfactory/txs.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package testfactory

import (
"bytes"
crand "crypto/rand"
"math/rand"

Expand Down Expand Up @@ -31,3 +32,28 @@ func GenerateRandomTxs(count, size int) types.Txs {
}
return txs
}

// GetRandomSubSlice returns two integers representing a randomly sized range in the interval [0, size]
func GetRandomSubSlice(size int) (start int, length int) {
length = rand.Intn(size + 1)
start = rand.Intn(size - length + 1)
return start, length
}

// CheckSubArray returns whether subTxList is a subarray of txList
func CheckSubArray(txList []types.Tx, subTxList []types.Tx) bool {
for i := 0; i <= len(txList)-len(subTxList); i++ {
j := 0
for j = 0; j < len(subTxList); j++ {
tx := txList[i+j]
subTx := subTxList[j]
if !bytes.Equal([]byte(tx), []byte(subTx)) {
break
}
}
if j == len(subTxList) {
return true
}
}
return false
}

0 comments on commit d5276b2

Please sign in to comment.