diff --git a/internal/test/factory.go b/internal/test/factory.go new file mode 100644 index 0000000..4272d82 --- /dev/null +++ b/internal/test/factory.go @@ -0,0 +1,112 @@ +package test + +import ( + crand "crypto/rand" + "encoding/binary" + "fmt" + "math/rand" + + "github.com/celestiaorg/go-square/pkg/blob" + "github.com/celestiaorg/go-square/pkg/namespace" + "github.com/celestiaorg/go-square/pkg/shares" +) + +var DefaultTestNamespace = namespace.MustNewV0([]byte("test")) + +func GenerateTxs(minSize, maxSize, numTxs int) [][]byte { + txs := make([][]byte, numTxs) + for i := 0; i < numTxs; i++ { + txs[i] = GenerateRandomTx(minSize, maxSize) + } + return txs +} + +func GenerateRandomTx(minSize, maxSize int) []byte { + size := minSize + if maxSize > minSize { + size = rand.Intn(maxSize-minSize) + minSize + } + return RandomBytes(size) +} + +func RandomBytes(size int) []byte { + b := make([]byte, size) + _, err := crand.Read(b) + if err != nil { + panic(err) + } + return b +} + +func GenerateBlobTxWithNamespace(namespaces []namespace.Namespace, blobSizes []int) []byte { + blobs := make([]*blob.Blob, len(blobSizes)) + if len(namespaces) != len(blobSizes) { + panic("number of namespaces should match number of blob sizes") + } + for i, size := range blobSizes { + blobs[i] = blob.New(namespaces[i], RandomBytes(size), shares.DefaultShareVersion) + } + blobTx, err := blob.MarshalBlobTx(MockPFB(toUint32(blobSizes)), blobs...) + if err != nil { + panic(err) + } + return blobTx +} + +func GenerateBlobTx(blobSizes []int) []byte { + return GenerateBlobTxWithNamespace(Repeat(DefaultTestNamespace, len(blobSizes)), blobSizes) +} + +func GenerateBlobTxs(numTxs, blobsPerPfb, blobSize int) [][]byte { + blobSizes := make([]int, blobsPerPfb) + for i := range blobSizes { + blobSizes[i] = blobSize + } + txs := make([][]byte, numTxs) + for i := 0; i < numTxs; i++ { + txs[i] = GenerateBlobTx(blobSizes) + } + return txs +} + +const mockPFBExtraBytes = 329 + +func MockPFB(blobSizes []uint32) []byte { + if len(blobSizes) == 0 { + panic("must have at least one blob") + } + tx := make([]byte, len(blobSizes)*4) + for i, size := range blobSizes { + binary.BigEndian.PutUint32(tx[i*4:], uint32(size)) + } + + return append(RandomBytes(mockPFBExtraBytes), tx...) +} + +func DecodeMockPFB(pfb []byte) ([]uint32, error) { + if len(pfb) < mockPFBExtraBytes+4 { + return nil, fmt.Errorf("must have a length of at least %d bytes, got %d", mockPFBExtraBytes+4, len(pfb)) + } + pfb = pfb[mockPFBExtraBytes:] + blobSizes := make([]uint32, len(pfb)/4) + for i := 0; i < len(blobSizes); i++ { + blobSizes[i] = binary.BigEndian.Uint32(pfb[i*4 : (i+1)*4]) + } + return blobSizes, nil +} + +func toUint32(arr []int) []uint32 { + output := make([]uint32, len(arr)) + for i, value := range arr { + output[i] = uint32(value) + } + return output +} + +func Repeat[T any](s T, count int) []T { + ss := make([]T, count) + for i := 0; i < count; i++ { + ss[i] = s + } + return ss +} diff --git a/internal/test/factory_test.go b/internal/test/factory_test.go new file mode 100644 index 0000000..7be95d9 --- /dev/null +++ b/internal/test/factory_test.go @@ -0,0 +1,21 @@ +package test_test + +import ( + "testing" + + "github.com/celestiaorg/go-square/internal/test" + "github.com/stretchr/testify/require" +) + +func TestPFBParity(t *testing.T) { + blobSizes := []uint32{20, 30, 10} + pfb := test.MockPFB(blobSizes) + output, err := test.DecodeMockPFB(pfb) + require.NoError(t, err) + require.Equal(t, blobSizes, output) + + require.Panics(t, func() { test.MockPFB(nil) }) + + _, err = test.DecodeMockPFB(test.RandomBytes(20)) + require.Error(t, err) +} diff --git a/merkle/proof_test.go b/merkle/proof_test.go index abaea7d..033f085 100644 --- a/merkle/proof_test.go +++ b/merkle/proof_test.go @@ -211,12 +211,18 @@ func TestProofValidateBasic(t *testing.T) { {"Good", func(sp *Proof) {}, ""}, {"Negative Total", func(sp *Proof) { sp.Total = -1 }, "negative Total"}, {"Negative Index", func(sp *Proof) { sp.Index = -1 }, "negative Index"}, - {"Invalid LeafHash", func(sp *Proof) { sp.LeafHash = make([]byte, 10) }, - "expected LeafHash size to be 32, got 10"}, - {"Too many Aunts", func(sp *Proof) { sp.Aunts = make([][]byte, MaxAunts+1) }, - "expected no more than 100 aunts, got 101"}, - {"Invalid Aunt", func(sp *Proof) { sp.Aunts[0] = make([]byte, 10) }, - "expected Aunts#0 size to be 32, got 10"}, + { + "Invalid LeafHash", func(sp *Proof) { sp.LeafHash = make([]byte, 10) }, + "expected LeafHash size to be 32, got 10", + }, + { + "Too many Aunts", func(sp *Proof) { sp.Aunts = make([][]byte, MaxAunts+1) }, + "expected no more than 100 aunts, got 101", + }, + { + "Invalid Aunt", func(sp *Proof) { sp.Aunts[0] = make([]byte, 10) }, + "expected Aunts#0 size to be 32, got 10", + }, } for _, tc := range testCases { @@ -235,8 +241,8 @@ func TestProofValidateBasic(t *testing.T) { }) } } -func TestVoteProtobuf(t *testing.T) { +func TestVoteProtobuf(t *testing.T) { _, proofs := ProofsFromByteSlices([][]byte{ []byte("apple"), []byte("watermelon"), diff --git a/merkle/tree_test.go b/merkle/tree_test.go index 78c1783..f529e35 100644 --- a/merkle/tree_test.go +++ b/merkle/tree_test.go @@ -42,7 +42,6 @@ func TestHashFromByteSlices(t *testing.T) { } func TestProof(t *testing.T) { - // Try an empty proof first rootHash, proofs := ProofsFromByteSlices([][]byte{}) require.Equal(t, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", hex.EncodeToString(rootHash)) @@ -100,7 +99,6 @@ func TestProof(t *testing.T) { } func TestHashAlternatives(t *testing.T) { - total := 100 items := make([][]byte, total) diff --git a/pkg/blob/blob.go b/pkg/blob/blob.go index 3ab1973..eaa3fd3 100644 --- a/pkg/blob/blob.go +++ b/pkg/blob/blob.go @@ -11,10 +11,8 @@ import ( "google.golang.org/protobuf/proto" ) -var ( - // SupportedBlobNamespaceVersions is a list of namespace versions that can be specified by a user for blobs. - SupportedBlobNamespaceVersions = []uint8{namespace.NamespaceVersionZero} -) +// SupportedBlobNamespaceVersions is a list of namespace versions that can be specified by a user for blobs. +var SupportedBlobNamespaceVersions = []uint8{namespace.NamespaceVersionZero} // ProtoBlobTxTypeID is included in each encoded BlobTx to help prevent // decoding binaries that are not actually BlobTxs. diff --git a/pkg/shares/consts.go b/pkg/shares/consts.go index b169874..b9b8070 100644 --- a/pkg/shares/consts.go +++ b/pkg/shares/consts.go @@ -54,7 +54,5 @@ const ( MaxShareVersion = 127 ) -var ( - // SupportedShareVersions is a list of supported share versions. - SupportedShareVersions = []uint8{ShareVersionZero} -) +// SupportedShareVersions is a list of supported share versions. +var SupportedShareVersions = []uint8{ShareVersionZero} diff --git a/pkg/shares/split_compact_shares_test.go b/pkg/shares/split_compact_shares_test.go index f8fad0e..4daf816 100644 --- a/pkg/shares/split_compact_shares_test.go +++ b/pkg/shares/split_compact_shares_test.go @@ -17,7 +17,7 @@ func TestCount(t *testing.T) { } testCases := []testCase{ {transactions: [][]byte{}, wantShareCount: 0}, - {transactions: [][]byte{[]byte{0}}, wantShareCount: 1}, + {transactions: [][]byte{{0}}, wantShareCount: 1}, {transactions: [][]byte{bytes.Repeat([]byte{1}, 100)}, wantShareCount: 1}, // Test with 1 byte over 1 share {transactions: [][]byte{bytes.Repeat([]byte{1}, RawTxSize(FirstCompactShareContentSize+1))}, wantShareCount: 2}, @@ -179,7 +179,7 @@ func TestWriteAndExportIdempotence(t *testing.T) { bytes.Repeat([]byte{0xf}, RawTxSize(FirstCompactShareContentSize)), bytes.Repeat([]byte{0xf}, RawTxSize(ContinuationCompactShareContentSize)), bytes.Repeat([]byte{0xf}, RawTxSize(ContinuationCompactShareContentSize)), - []byte{0xf}, + {0xf}, }, wantLen: 4, }, diff --git a/pkg/square/builder_test.go b/pkg/square/builder_test.go index ef2f717..e986f17 100644 --- a/pkg/square/builder_test.go +++ b/pkg/square/builder_test.go @@ -2,47 +2,50 @@ package square_test import ( "bytes" + "fmt" + "math/rand" "testing" + "github.com/celestiaorg/go-square/internal/test" + "github.com/celestiaorg/go-square/pkg/blob" + "github.com/celestiaorg/go-square/pkg/namespace" "github.com/celestiaorg/go-square/pkg/shares" "github.com/celestiaorg/go-square/pkg/square" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -// func TestBuilderSquareSizeEstimation(t *testing.T) { -// type test struct { -// name string -// normalTxs int -// pfbCount, pfbSize int -// expectedSquareSize int -// } -// tests := []test{ -// {"empty block", 0, 0, 0, appconsts.MinSquareSize}, -// {"one normal tx", 1, 0, 0, 1}, -// {"one small pfb small block", 0, 1, 100, 2}, -// {"mixed small block", 10, 12, 500, 8}, -// {"small block 2", 0, 12, 1000, 8}, -// {"mixed medium block 2", 10, 20, 10000, 32}, -// {"one large pfb large block", 0, 1, 1000000, 64}, -// {"one hundred large pfb large block", 0, 100, 100000, appconsts.DefaultGovMaxSquareSize}, -// {"one hundred large pfb medium block", 100, 100, 100000, appconsts.DefaultGovMaxSquareSize}, -// {"mixed transactions large block", 100, 100, 100000, appconsts.DefaultGovMaxSquareSize}, -// {"mixed transactions large block 2", 1000, 1000, 10000, appconsts.DefaultGovMaxSquareSize}, -// {"mostly transactions large block", 10000, 1000, 100, appconsts.DefaultGovMaxSquareSize}, -// {"only small pfb large block", 0, 10000, 1, appconsts.DefaultGovMaxSquareSize}, -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// rand := tmrand.NewRand() -// signer, err := testnode.NewOfflineSigner() -// require.NoError(t, err) -// txs := generateMixedTxs(signer, rand, tt.normalTxs, tt.pfbCount, 1, tt.pfbSize) -// square, _, err := square.Build(txs, appconsts.LatestVersion, appconsts.DefaultGovMaxSquareSize) -// require.NoError(t, err) -// require.EqualValues(t, tt.expectedSquareSize, square.Size()) -// }) -// } -// } +func TestBuilderSquareSizeEstimation(t *testing.T) { + type test struct { + name string + normalTxs int + pfbCount, pfbSize int + expectedSquareSize int + } + tests := []test{ + {"empty block", 0, 0, 0, 1}, + {"one normal tx", 1, 0, 0, 1}, + {"one small pfb small block", 0, 1, 100, 2}, + {"mixed small block", 10, 12, 500, 8}, + {"small block 2", 0, 12, 1000, 8}, + {"mixed medium block 2", 10, 20, 10000, 32}, + {"one large pfb large block", 0, 1, 1000000, 64}, + {"one hundred large pfb large block", 0, 100, 100000, 64}, + {"one hundred large pfb medium block", 100, 100, 100000, 64}, + {"mixed transactions large block", 100, 100, 100000, 64}, + {"mixed transactions large block 2", 1000, 1000, 10000, 64}, + {"mostly transactions large block", 10000, 1000, 100, 64}, + {"only small pfb large block", 0, 10000, 1, 64}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + txs := generateMixedTxs(tt.normalTxs, tt.pfbCount, 1, tt.pfbSize) + square, _, err := square.Build(txs, 64, defaultSubtreeRootThreshold) + require.NoError(t, err) + require.EqualValues(t, tt.expectedSquareSize, square.Size()) + }) + } +} func TestBuilderRejectsTransactions(t *testing.T) { builder, err := square.NewBuilder(2, 64) // 2 x 2 square @@ -52,51 +55,42 @@ func TestBuilderRejectsTransactions(t *testing.T) { require.False(t, builder.AppendTx(newTx(1))) } -// func TestBuilderRejectsBlobTransactions(t *testing.T) { -// ns1 := namespace.MustNewV0(bytes.Repeat([]byte{1}, namespace.NamespaceVersionZeroIDSize)) -// testCases := []struct { -// blobSize []int -// added bool -// }{ -// { -// blobSize: []int{shares.AvailableBytesFromSparseShares(3) + 1}, -// added: false, -// }, -// { -// blobSize: []int{shares.AvailableBytesFromSparseShares(3)}, -// added: true, -// }, -// { -// blobSize: []int{shares.AvailableBytesFromSparseShares(2) + 1, shares.AvailableBytesFromSparseShares(1)}, -// added: false, -// }, -// { -// blobSize: []int{shares.AvailableBytesFromSparseShares(1), shares.AvailableBytesFromSparseShares(1)}, -// added: true, -// }, -// { -// // fun fact: three blobs increases the size of the PFB to two shares, hence this fails -// blobSize: []int{ -// shares.AvailableBytesFromSparseShares(1), -// shares.AvailableBytesFromSparseShares(1), -// shares.AvailableBytesFromSparseShares(1), -// }, -// added: false, -// }, -// } +func TestBuilderRejectsBlobTransactions(t *testing.T) { + ns1 := namespace.MustNewV0(bytes.Repeat([]byte{1}, namespace.NamespaceVersionZeroIDSize)) + testCases := []struct { + blobSize []int + added bool + }{ + { + blobSize: []int{shares.AvailableBytesFromSparseShares(3) + 1}, + added: false, + }, + { + blobSize: []int{shares.AvailableBytesFromSparseShares(3)}, + added: true, + }, + { + blobSize: []int{shares.AvailableBytesFromSparseShares(2) + 1, shares.AvailableBytesFromSparseShares(1)}, + added: false, + }, + { + blobSize: []int{shares.AvailableBytesFromSparseShares(1), shares.AvailableBytesFromSparseShares(1)}, + added: true, + }, + } -// for idx, tc := range testCases { -// t.Run(fmt.Sprintf("case%d", idx), func(t *testing.T) { -// builder, err := square.NewBuilder(2, 64) -// require.NoError(t, err) -// txs := generateBlobTxsWithNamespaces(t, ns1.Repeat(len(tc.blobSize)), [][]int{tc.blobSize}) -// require.Len(t, txs, 1) -// blobTx, isBlobTx := blob.UnmarshalBlobTx(txs[0]) -// require.True(t, isBlobTx) -// require.Equal(t, tc.added, builder.AppendBlobTx(blobTx)) -// }) -// } -// } + for idx, tc := range testCases { + t.Run(fmt.Sprintf("case%d", idx), func(t *testing.T) { + builder, err := square.NewBuilder(2, 64) + require.NoError(t, err) + txs := generateBlobTxsWithNamespaces(ns1.Repeat(len(tc.blobSize)), [][]int{tc.blobSize}) + require.Len(t, txs, 1) + blobTx, isBlobTx := blob.UnmarshalBlobTx(txs[0]) + require.True(t, isBlobTx) + require.Equal(t, tc.added, builder.AppendBlobTx(blobTx)) + }) + } +} func TestBuilderInvalidConstructor(t *testing.T) { _, err := square.NewBuilder(-4, 64) @@ -111,43 +105,40 @@ func newTx(len int) []byte { return bytes.Repeat([]byte{0}, shares.RawTxSize(len)) } -// func TestBuilderFindTxShareRange(t *testing.T) { -// signer, err := testnode.NewOfflineSigner() -// require.NoError(t, err) -// blockTxs := testfactory.GenerateRandomTxs(5, 900).ToSliceOfBytes() -// blockTxs = append(blockTxs, blobfactory.RandBlobTxsRandomlySized(signer, tmrand.NewRand(), 5, 1000, 10).ToSliceOfBytes()...) -// require.Len(t, blockTxs, 10) +func TestBuilderFindTxShareRange(t *testing.T) { + blockTxs := generateOrderedTxs(5, 5, 1000, 10) + require.Len(t, blockTxs, 10) -// builder, err := square.NewBuilder(128, 64, blockTxs...) -// require.NoError(t, err) + builder, err := square.NewBuilder(128, 64, blockTxs...) + require.NoError(t, err) -// dataSquare, err := builder.Export() -// require.NoError(t, err) -// size := dataSquare.Size() * dataSquare.Size() + dataSquare, err := builder.Export() + require.NoError(t, err) + size := dataSquare.Size() * dataSquare.Size() -// var lastEnd int -// for idx, tx := range blockTxs { -// blobTx, isBlobTx := blob.UnmarshalBlobTx(tx) -// if isBlobTx { -// tx = blobTx.Tx -// } -// shareRange, err := builder.FindTxShareRange(idx) -// require.NoError(t, err) -// if idx == 5 { -// // normal txs and PFBs use a different namespace so there -// // can't be any overlap in the index -// require.Greater(t, shareRange.Start, lastEnd-1) -// } else { -// require.GreaterOrEqual(t, shareRange.Start, lastEnd-1) -// } -// require.LessOrEqual(t, shareRange.End, size) -// txShares := dataSquare[shareRange.Start : shareRange.End+1] -// parsedShares, err := rawData(txShares) -// require.NoError(t, err) -// require.True(t, bytes.Contains(parsedShares, tx)) -// lastEnd = shareRange.End -// } -// } + var lastEnd int + for idx, tx := range blockTxs { + blobTx, isBlobTx := blob.UnmarshalBlobTx(tx) + if isBlobTx { + tx = blobTx.Tx + } + shareRange, err := builder.FindTxShareRange(idx) + require.NoError(t, err) + if idx == 5 { + // normal txs and PFBs use a different namespace so there + // can't be any overlap in the index + require.Greater(t, shareRange.Start, lastEnd-1) + } else { + require.GreaterOrEqual(t, shareRange.Start, lastEnd-1) + } + require.LessOrEqual(t, shareRange.End, size) + txShares := dataSquare[shareRange.Start : shareRange.End+1] + parsedShares, err := rawData(txShares) + require.NoError(t, err) + require.True(t, bytes.Contains(parsedShares, tx)) + lastEnd = shareRange.End + } +} func rawData(shares []shares.Share) ([]byte, error) { var data []byte @@ -161,186 +152,192 @@ func rawData(shares []shares.Share) ([]byte, error) { return data, nil } -// // TestSquareBlobPositions ensures that the share commitment rules which dictate the padding -// // between blobs is followed as well as the ordering of blobs by namespace. -// func TestSquareBlobPostions(t *testing.T) { -// ns1 := namespace.MustNewV0(bytes.Repeat([]byte{1}, namespace.NamespaceVersionZeroIDSize)) -// ns2 := namespace.MustNewV0(bytes.Repeat([]byte{2}, namespace.NamespaceVersionZeroIDSize)) -// ns3 := namespace.MustNewV0(bytes.Repeat([]byte{3}, namespace.NamespaceVersionZeroIDSize)) +// TestSquareBlobPositions ensures that the share commitment rules which dictate the padding +// between blobs is followed as well as the ordering of blobs by namespace. +func TestSquareBlobPostions(t *testing.T) { + ns1 := namespace.MustNewV0(bytes.Repeat([]byte{1}, namespace.NamespaceVersionZeroIDSize)) + ns2 := namespace.MustNewV0(bytes.Repeat([]byte{2}, namespace.NamespaceVersionZeroIDSize)) + ns3 := namespace.MustNewV0(bytes.Repeat([]byte{3}, namespace.NamespaceVersionZeroIDSize)) -// type test struct { -// squareSize int -// blobTxs [][]byte -// expectedIndexes [][]uint32 -// } -// tests := []test{ -// { -// squareSize: 4, -// blobTxs: generateBlobTxsWithNamespaces( -// t, -// []namespace.Namespace{ns1}, -// [][]int{{1}}, -// ), -// expectedIndexes: [][]uint32{{1}}, -// }, -// { -// squareSize: 4, -// blobTxs: generateBlobTxsWithNamespaces( -// t, -// []namespace.Namespace{ns1, ns1}, -// repeat([]int{100}, 2), -// ), -// expectedIndexes: [][]uint32{{2}, {3}}, -// }, -// { -// squareSize: 4, -// blobTxs: generateBlobTxsWithNamespaces( -// t, -// []namespace.Namespace{ns1, ns1, ns1, ns1, ns1, ns1, ns1, ns1, ns1}, -// repeat([]int{100}, 9), -// ), -// expectedIndexes: [][]uint32{{7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}, {15}}, -// }, -// { -// squareSize: 4, -// blobTxs: generateBlobTxsWithNamespaces( -// t, -// []namespace.Namespace{ns1, ns1, ns1}, -// [][]int{{10000}, {10000}, {1000000}}, -// ), -// expectedIndexes: [][]uint32{}, -// }, -// { -// squareSize: 64, -// blobTxs: generateBlobTxsWithNamespaces( -// t, -// []namespace.Namespace{ns1, ns1, ns1}, -// [][]int{{1000}, {10000}, {10000}}, -// ), -// expectedIndexes: [][]uint32{{3}, {6}, {27}}, -// }, -// { -// squareSize: 32, -// blobTxs: generateBlobTxsWithNamespaces( -// t, -// []namespace.Namespace{ns2, ns1, ns1}, -// [][]int{{100}, {100}, {100}}, -// ), -// expectedIndexes: [][]uint32{{5}, {3}, {4}}, -// }, -// { -// squareSize: 16, -// blobTxs: generateBlobTxsWithNamespaces( -// t, -// []namespace.Namespace{ns1, ns2, ns1}, -// [][]int{{100}, {900}, {900}}, // 1, 2, 2 shares respectively -// ), -// expectedIndexes: [][]uint32{{3}, {6}, {4}}, -// }, -// { -// squareSize: 4, -// blobTxs: generateBlobTxsWithNamespaces( -// t, -// []namespace.Namespace{ns1, ns3, ns3, ns2}, -// [][]int{{100}, {1000, 1000}, {420}}, -// ), -// expectedIndexes: [][]uint32{{3}, {5, 8}, {4}}, -// }, -// { -// // no blob txs should make it in the square -// squareSize: 1, -// blobTxs: generateBlobTxsWithNamespaces( -// t, -// []namespace.Namespace{ns1, ns2, ns3}, -// [][]int{{1000}, {1000}, {1000}}, -// ), -// expectedIndexes: [][]uint32{}, -// }, -// { -// // only two blob txs should make it in the square (after reordering) -// squareSize: 4, -// blobTxs: generateBlobTxsWithNamespaces( -// t, -// []namespace.Namespace{ns3, ns2, ns1}, -// [][]int{{2000}, {2000}, {5000}}, -// ), -// expectedIndexes: [][]uint32{{7}, {2}}, -// }, -// { -// squareSize: 4, -// blobTxs: generateBlobTxsWithNamespaces( -// t, -// []namespace.Namespace{ns3, ns3, ns2, ns1}, -// [][]int{{1800, 1000}, {22000}, {1800}}, -// ), -// // should be ns1 and {ns3, ns3} as ns2 is too large -// expectedIndexes: [][]uint32{{6, 10}, {2}}, -// }, -// { -// squareSize: 4, -// blobTxs: generateBlobTxsWithNamespaces( -// t, -// []namespace.Namespace{ns1, ns3, ns3, ns1, ns2, ns2}, -// [][]int{{100}, {1400, 900, 200, 200}, {420}}, -// ), -// expectedIndexes: [][]uint32{{3}, {7, 10, 4, 5}, {6}}, -// }, -// { -// squareSize: 4, -// blobTxs: generateBlobTxsWithNamespaces( -// t, -// []namespace.Namespace{ns1, ns3, ns3, ns1, ns2, ns2}, -// [][]int{{100}, {900, 1400, 200, 200}, {420}}, -// ), -// expectedIndexes: [][]uint32{{3}, {7, 9, 4, 5}, {6}}, -// }, -// { -// squareSize: 16, -// blobTxs: generateBlobTxsWithNamespaces( -// t, -// []namespace.Namespace{ns1, ns1}, -// [][]int{{100}, {shares.AvailableBytesFromSparseShares(64)}}, -// ), -// // There should be one share padding between the two blobs -// expectedIndexes: [][]uint32{{2}, {3}}, -// }, -// { -// squareSize: 16, -// blobTxs: generateBlobTxsWithNamespaces( -// t, -// []namespace.Namespace{ns1, ns1}, -// [][]int{{100}, {shares.AvailableBytesFromSparseShares(64) + 1}}, -// ), -// // There should be one share padding between the two blobs -// expectedIndexes: [][]uint32{{2}, {4}}, -// }, -// } -// for i, tt := range tests { -// t.Run(fmt.Sprintf("case%d", i), func(t *testing.T) { -// builder, err := square.NewBuilder(tt.squareSize, 64) -// require.NoError(t, err) -// for _, tx := range tt.blobTxs { -// blobTx, isBlobTx := blob.UnmarshalBlobTx(tx) -// require.True(t, isBlobTx) -// _ = builder.AppendBlobTx(blobTx) -// } -// square, err := builder.Export() -// require.NoError(t, err) -// txs, err := shares.ParseTxs(square) -// require.NoError(t, err) -// for j, tx := range txs { -// wrappedPFB, isWrappedPFB := blob.UnmarshalIndexWrapper(tx) -// require.True(t, isWrappedPFB) -// require.Equal(t, tt.expectedIndexes[j], wrappedPFB.ShareIndexes, j) -// } -// }) -// } -// } + type testCase struct { + squareSize int + blobTxs [][]byte + expectedIndexes [][]uint32 + } + tests := []testCase{ + { + squareSize: 4, + blobTxs: generateBlobTxsWithNamespaces( + []namespace.Namespace{ns1}, + [][]int{{1}}, + ), + expectedIndexes: [][]uint32{{1}}, + }, + { + squareSize: 4, + blobTxs: generateBlobTxsWithNamespaces( + []namespace.Namespace{ns1, ns1}, + test.Repeat([]int{100}, 2), + ), + expectedIndexes: [][]uint32{{2}, {3}}, + }, + { + squareSize: 4, + blobTxs: generateBlobTxsWithNamespaces( + []namespace.Namespace{ns1, ns1, ns1, ns1, ns1, ns1, ns1, ns1, ns1}, + test.Repeat([]int{100}, 9), + ), + expectedIndexes: [][]uint32{{7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}, {15}}, + }, + { + squareSize: 4, + blobTxs: generateBlobTxsWithNamespaces( + []namespace.Namespace{ns1, ns1, ns1}, + [][]int{{10000}, {10000}, {1000000}}, + ), + expectedIndexes: [][]uint32{}, + }, + { + squareSize: 64, + blobTxs: generateBlobTxsWithNamespaces( + []namespace.Namespace{ns1, ns1, ns1}, + [][]int{{1000}, {10000}, {10000}}, + ), + expectedIndexes: [][]uint32{{3}, {6}, {27}}, + }, + { + squareSize: 32, + blobTxs: generateBlobTxsWithNamespaces( + []namespace.Namespace{ns2, ns1, ns1}, + [][]int{{100}, {100}, {100}}, + ), + expectedIndexes: [][]uint32{{5}, {3}, {4}}, + }, + { + squareSize: 16, + blobTxs: generateBlobTxsWithNamespaces( + []namespace.Namespace{ns1, ns2, ns1}, + [][]int{{100}, {900}, {900}}, // 1, 2, 2 shares respectively + ), + expectedIndexes: [][]uint32{{3}, {6}, {4}}, + }, + { + squareSize: 4, + blobTxs: generateBlobTxsWithNamespaces( + []namespace.Namespace{ns1, ns3, ns3, ns2}, + [][]int{{100}, {1000, 1000}, {420}}, + ), + expectedIndexes: [][]uint32{{3}, {5, 8}, {4}}, + }, + { + // no blob txs should make it in the square + squareSize: 1, + blobTxs: generateBlobTxsWithNamespaces( + []namespace.Namespace{ns1, ns2, ns3}, + [][]int{{1000}, {1000}, {1000}}, + ), + expectedIndexes: [][]uint32{}, + }, + { + // only two blob txs should make it in the square (after reordering) + squareSize: 4, + blobTxs: generateBlobTxsWithNamespaces( + []namespace.Namespace{ns3, ns2, ns1}, + [][]int{{2000}, {2000}, {5000}}, + ), + expectedIndexes: [][]uint32{{7}, {2}}, + }, + { + squareSize: 4, + blobTxs: generateBlobTxsWithNamespaces( + []namespace.Namespace{ns3, ns3, ns2, ns1}, + [][]int{{1800, 1000}, {22000}, {1800}}, + ), + // should be ns1 and {ns3, ns3} as ns2 is too large + expectedIndexes: [][]uint32{{6, 10}, {2}}, + }, + { + squareSize: 4, + blobTxs: generateBlobTxsWithNamespaces( + []namespace.Namespace{ns1, ns3, ns3, ns1, ns2, ns2}, + [][]int{{100}, {1400, 900, 200, 200}, {420}}, + ), + expectedIndexes: [][]uint32{{3}, {7, 10, 4, 5}, {6}}, + }, + { + squareSize: 4, + blobTxs: generateBlobTxsWithNamespaces( + []namespace.Namespace{ns1, ns3, ns3, ns1, ns2, ns2}, + [][]int{{100}, {900, 1400, 200, 200}, {420}}, + ), + expectedIndexes: [][]uint32{{3}, {7, 9, 4, 5}, {6}}, + }, + { + squareSize: 16, + blobTxs: generateBlobTxsWithNamespaces( + []namespace.Namespace{ns1, ns1}, + [][]int{{100}, {shares.AvailableBytesFromSparseShares(64)}}, + ), + // There should be one share padding between the two blobs + expectedIndexes: [][]uint32{{2}, {3}}, + }, + { + squareSize: 16, + blobTxs: generateBlobTxsWithNamespaces( + []namespace.Namespace{ns1, ns1}, + [][]int{{100}, {shares.AvailableBytesFromSparseShares(64) + 1}}, + ), + // There should be one share padding between the two blobs + expectedIndexes: [][]uint32{{2}, {4}}, + }, + } + for i, tt := range tests { + t.Run(fmt.Sprintf("case%d", i), func(t *testing.T) { + builder, err := square.NewBuilder(tt.squareSize, defaultSubtreeRootThreshold) + require.NoError(t, err) + for _, tx := range tt.blobTxs { + blobTx, isBlobTx := blob.UnmarshalBlobTx(tx) + require.True(t, isBlobTx) + _ = builder.AppendBlobTx(blobTx) + } + square, err := builder.Export() + require.NoError(t, err) + txs, err := shares.ParseTxs(square) + require.NoError(t, err) + for j, tx := range txs { + wrappedPFB, isWrappedPFB := blob.UnmarshalIndexWrapper(tx) + assert.True(t, isWrappedPFB) + assert.Equal(t, tt.expectedIndexes[j], wrappedPFB.ShareIndexes, j) + } + }) + } +} -// func repeat[T any](s T, count int) []T { -// ss := make([]T, count) -// for i := 0; i < count; i++ { -// ss[i] = s -// } -// return ss -// } +func generateMixedTxs(normalTxCount, pfbCount, blobsPerPfb, blobSize int) [][]byte { + return shuffle(generateOrderedTxs(normalTxCount, pfbCount, blobsPerPfb, blobSize)) +} + +func generateOrderedTxs(normalTxCount, pfbCount, blobsPerPfb, blobSize int) [][]byte { + pfbTxs := test.GenerateBlobTxs(pfbCount, blobsPerPfb, blobSize) + normieTxs := test.GenerateTxs(200, 400, normalTxCount) + return append(normieTxs, pfbTxs...) +} + +func shuffle(slice [][]byte) [][]byte { + for i := range slice { + j := rand.Intn(i + 1) + slice[i], slice[j] = slice[j], slice[i] + } + return slice +} + +func generateBlobTxsWithNamespaces(namespaces []namespace.Namespace, blobSizes [][]int) [][]byte { + txs := make([][]byte, len(blobSizes)) + counter := 0 + for i := 0; i < len(txs); i++ { + n := namespaces[counter : counter+len(blobSizes[i])] + txs[i] = test.GenerateBlobTxWithNamespace(n, blobSizes[i]) + counter += len(blobSizes[i]) + } + return txs +} diff --git a/pkg/square/square_benchmark_test.go b/pkg/square/square_benchmark_test.go index 03e7f74..0545d9d 100644 --- a/pkg/square/square_benchmark_test.go +++ b/pkg/square/square_benchmark_test.go @@ -1,44 +1,46 @@ package square_test -// func BenchmarkSquareConstruct(b *testing.B) { -// for _, txCount := range []int{10, 100, 1000} { -// b.Run(fmt.Sprintf("txCount=%d", txCount), func(b *testing.B) { -// signer, err := testnode.NewOfflineSigner() -// require.NoError(b, err) -// txs := generateOrderedTxs(signer, tmrand.NewRand(), txCount/2, txCount/2, 1, 1024) -// b.ResetTimer() -// for i := 0; i < b.N; i++ { -// _, err := square.Construct(txs, LatestVersion, DefaultSquareSizeUpperBound) -// require.NoError(b, err) -// } -// }) -// } -// } +import ( + "fmt" + "testing" -// func BenchmarkSquareBuild(b *testing.B) { -// for _, txCount := range []int{10, 100, 1000, 10000} { -// b.Run(fmt.Sprintf("txCount=%d", txCount), func(b *testing.B) { -// signer, err := testnode.NewOfflineSigner() -// require.NoError(b, err) -// txs := generateMixedTxs(signer, tmrand.NewRand(), txCount/2, txCount/2, 1, 1024) -// b.ResetTimer() -// for i := 0; i < b.N; i++ { -// _, _, err := square.Build(txs, LatestVersion, DefaultSquareSizeUpperBound) -// require.NoError(b, err) -// } -// }) -// } -// const txCount = 10 -// for _, blobSize := range []int{10, 100, 1000, 10000} { -// b.Run(fmt.Sprintf("blobSize=%d", blobSize), func(b *testing.B) { -// signer, err := testnode.NewOfflineSigner() -// require.NoError(b, err) -// txs := generateMixedTxs(signer, tmrand.NewRand(), 0, txCount, 1, blobSize) -// b.ResetTimer() -// for i := 0; i < b.N; i++ { -// _, _, err := square.Build(txs, LatestVersion, DefaultSquareSizeUpperBound) -// require.NoError(b, err) -// } -// }) -// } -// } + "github.com/celestiaorg/go-square/pkg/square" + "github.com/stretchr/testify/require" +) + +func BenchmarkSquareConstruct(b *testing.B) { + for _, txCount := range []int{10, 100, 1000} { + b.Run(fmt.Sprintf("txCount=%d", txCount), func(b *testing.B) { + txs := generateOrderedTxs(txCount/2, txCount/2, 1, 1024) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := square.Construct(txs, defaultMaxSquareSize, defaultSubtreeRootThreshold) + require.NoError(b, err) + } + }) + } +} + +func BenchmarkSquareBuild(b *testing.B) { + for _, txCount := range []int{10, 100, 1000, 10000} { + b.Run(fmt.Sprintf("txCount=%d", txCount), func(b *testing.B) { + txs := generateMixedTxs(txCount/2, txCount/2, 1, 1024) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _, err := square.Build(txs, defaultMaxSquareSize, defaultSubtreeRootThreshold) + require.NoError(b, err) + } + }) + } + const txCount = 10 + for _, blobSize := range []int{10, 100, 1000, 10000} { + b.Run(fmt.Sprintf("blobSize=%d", blobSize), func(b *testing.B) { + txs := generateMixedTxs(0, txCount, 1, blobSize) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _, err := square.Build(txs, defaultMaxSquareSize, defaultSubtreeRootThreshold) + require.NoError(b, err) + } + }) + } +} diff --git a/pkg/square/square_test.go b/pkg/square/square_test.go index 588f5db..c328609 100644 --- a/pkg/square/square_test.go +++ b/pkg/square/square_test.go @@ -2,8 +2,11 @@ package square_test import ( "bytes" + "fmt" "testing" + "github.com/celestiaorg/go-square/internal/test" + "github.com/celestiaorg/go-square/pkg/blob" "github.com/celestiaorg/go-square/pkg/shares" "github.com/celestiaorg/go-square/pkg/square" "github.com/stretchr/testify/assert" @@ -16,29 +19,26 @@ const ( defaultSubtreeRootThreshold = 64 ) -// func TestSquareConstruction(t *testing.T) { -// rand := tmrand.NewRand() -// signer, err := testnode.NewOfflineSigner() -// require.NoError(t, err) -// sendTxs := blobfactory.GenerateManyRawSendTxs(signer, 250) -// pfbTxs := blobfactory.RandBlobTxs(signer, rand, 10000, 1, 1024) -// t.Run("normal transactions after PFB transactions", func(t *testing.T) { -// txs := append(sendTxs[:5], append(pfbTxs, sendTxs[5:]...)...) -// _, err := square.Construct(txs, LatestVersion, defaultMaxSquareSize) -// require.Error(t, err) -// }) -// t.Run("not enough space to append transactions", func(t *testing.T) { -// _, err := square.Construct(sendTxs, LatestVersion, 2) -// require.Error(t, err) -// _, err = square.Construct(pfbTxs, LatestVersion, 2) -// require.Error(t, err) -// }) -// t.Run("construction should fail if a single PFB tx contains a blob that is too large to fit in the square", func(t *testing.T) { -// pfbTxs := blobfactory.RandBlobTxs(signer, rand, 1, 1, 2*mebibyte) -// _, err := square.Construct(pfbTxs, LatestVersion, 64) -// require.Error(t, err) -// }) -// } +func TestSquareConstruction(t *testing.T) { + sendTxs := test.GenerateTxs(250, 250, 250) + pfbTxs := test.GenerateBlobTxs(10_000, 1, 1024) + t.Run("normal transactions after PFB transactions", func(t *testing.T) { + txs := append(sendTxs[:5], append(pfbTxs, sendTxs[5:]...)...) + _, err := square.Construct(txs, defaultMaxSquareSize, defaultSubtreeRootThreshold) + require.Error(t, err) + }) + t.Run("not enough space to append transactions", func(t *testing.T) { + _, err := square.Construct(sendTxs, 2, defaultSubtreeRootThreshold) + require.Error(t, err) + _, err = square.Construct(pfbTxs, 2, defaultSubtreeRootThreshold) + require.Error(t, err) + }) + t.Run("construction should fail if a single PFB tx contains a blob that is too large to fit in the square", func(t *testing.T) { + pfbTxs := test.GenerateBlobTxs(1, 1, 2*mebibyte) + _, err := square.Construct(pfbTxs, 64, defaultSubtreeRootThreshold) + require.Error(t, err) + }) +} func TestSquareTxShareRange(t *testing.T) { type test struct { @@ -111,126 +111,80 @@ func TestSquareTxShareRange(t *testing.T) { } } -// func TestSquareBlobShareRange(t *testing.T) { -// signer, err := testnode.NewOfflineSigner() -// require.NoError(t, err) -// txs := blobfactory.RandBlobTxsRandomlySized(signer, tmrand.NewRand(), 10, 1000, 10).ToSliceOfBytes() - -// builder, err := square.NewBuilder(defaultMaxSquareSize, defaultSubtreeRootThreshold, txs...) -// require.NoError(t, err) - -// dataSquare, err := builder.Export() -// require.NoError(t, err) - -// for pfbIdx, tx := range txs { -// blobTx, isBlobTx := blob.UnmarshalBlobTx(tx) -// require.True(t, isBlobTx) -// for blobIdx := range blobTx.Blobs { -// shareRange, err := square.BlobShareRange(txs, pfbIdx, blobIdx, defaultMaxSquareSize, defaultSubtreeRootThreshold) -// require.NoError(t, err) -// require.LessOrEqual(t, shareRange.End, len(dataSquare)) -// blobShares := dataSquare[shareRange.Start:shareRange.End] -// blobSharesBytes, err := rawData(blobShares) -// require.NoError(t, err) -// require.True(t, bytes.Contains(blobSharesBytes, blobTx.Blobs[blobIdx].Data)) -// } -// } - -// // error on out of bounds cases -// _, err = square.BlobShareRange(txs, -1, 0, defaultMaxSquareSize, defaultSubtreeRootThreshold) -// require.Error(t, err) - -// _, err = square.BlobShareRange(txs, 0, -1, defaultMaxSquareSize, defaultSubtreeRootThreshold) -// require.Error(t, err) - -// _, err = square.BlobShareRange(txs, 10, 0, defaultMaxSquareSize, defaultSubtreeRootThreshold) -// require.Error(t, err) - -// _, err = square.BlobShareRange(txs, 0, 10, defaultMaxSquareSize, defaultSubtreeRootThreshold) -// require.Error(t, err) -// } - -// func TestSquareDeconstruct(t *testing.T) { -// rand := tmrand.NewRand() -// encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...) -// t.Run("ConstructDeconstructParity", func(t *testing.T) { -// // 8192 -> square size 128 -// for _, numTxs := range []int{2, 128, 1024, 8192} { -// t.Run(fmt.Sprintf("%d", numTxs), func(t *testing.T) { -// signer, err := testnode.NewOfflineSigner() -// require.NoError(t, err) -// txs := generateOrderedTxs(signer, rand, numTxs/2, numTxs/2, 1, 800) -// dataSquare, err := square.Construct(txs, LatestVersion, defaultMaxSquareSize) -// require.NoError(t, err) -// recomputedTxs, err := square.Deconstruct(dataSquare, encCfg.TxConfig.TxDecoder()) -// require.NoError(t, err) -// require.Equal(t, txs, recomputedTxs) -// }) -// } -// }) -// t.Run("NoPFBs", func(t *testing.T) { -// const numTxs = 10 -// signer, err := testnode.NewOfflineSigner() -// require.NoError(t, err) -// txs := coretypes.Txs(blobfactory.GenerateManyRawSendTxs(signer, numTxs)).ToSliceOfBytes() -// dataSquare, err := square.Construct(txs, LatestVersion, defaultMaxSquareSize) -// require.NoError(t, err) -// recomputedTxs, err := square.Deconstruct(dataSquare, encCfg.TxConfig.TxDecoder()) -// require.NoError(t, err) -// require.Equal(t, txs, recomputedTxs) -// }) -// t.Run("PFBsOnly", func(t *testing.T) { -// signer, err := testnode.NewOfflineSigner() -// require.NoError(t, err) -// txs := blobfactory.RandBlobTxs(signer, rand, 100, 1, 1024).ToSliceOfBytes() -// dataSquare, err := square.Construct(txs, LatestVersion, defaultMaxSquareSize) -// require.NoError(t, err) -// recomputedTxs, err := square.Deconstruct(dataSquare, encCfg.TxConfig.TxDecoder()) -// require.NoError(t, err) -// require.Equal(t, txs, recomputedTxs) -// }) -// t.Run("EmptySquare", func(t *testing.T) { -// tx, err := square.Deconstruct(square.EmptySquare(), encCfg.TxConfig.TxDecoder()) -// require.NoError(t, err) -// require.Equal(t, coretypes.Txs{}, tx) -// }) -// } - -// func TestSquareShareCommitments(t *testing.T) { -// const numTxs = 10 -// rand := tmrand.NewRand() -// signer, err := testnode.NewOfflineSigner() -// require.NoError(t, err) -// txs := generateOrderedTxs(signer, rand, numTxs, numTxs, 3, 800) -// builder, err := square.NewBuilder(defaultMaxSquareSize, LatestVersion, txs...) -// require.NoError(t, err) - -// dataSquare, err := builder.Export() -// require.NoError(t, err) - -// cacher := inclusion.NewSubtreeCacher(uint64(dataSquare.Size())) -// eds, err := rsmt2d.ComputeExtendedDataSquare(shares.ToBytes(dataSquare), DefaultCodec(), cacher.Constructor) -// require.NoError(t, err) -// dah, err := da.NewDataAvailabilityHeader(eds) -// require.NoError(t, err) -// decoder := encoding.MakeConfig(app.ModuleEncodingRegisters...).TxConfig.TxDecoder() - -// for pfbIndex := 0; pfbIndex < numTxs; pfbIndex++ { -// wpfb, err := builder.GetWrappedPFB(pfbIndex + numTxs) -// require.NoError(t, err) -// tx, err := decoder(wpfb.Tx) -// require.NoError(t, err) - -// pfb, ok := tx.GetMsgs()[0].(*blobtypes.MsgPayForBlobs) -// require.True(t, ok) - -// for blobIndex, shareIndex := range wpfb.ShareIndexes { -// commitment, err := inclusion.GetCommitment(cacher, dah, int(shareIndex), shares.SparseSharesNeeded(pfb.BlobSizes[blobIndex]), DefaultSubtreeRootThreshold) -// require.NoError(t, err) -// require.Equal(t, pfb.ShareCommitments[blobIndex], commitment) -// } -// } -// } +func TestSquareBlobShareRange(t *testing.T) { + txs := test.GenerateBlobTxs(10, 1, 1024) + + builder, err := square.NewBuilder(defaultMaxSquareSize, defaultSubtreeRootThreshold, txs...) + require.NoError(t, err) + + dataSquare, err := builder.Export() + require.NoError(t, err) + + for pfbIdx, tx := range txs { + blobTx, isBlobTx := blob.UnmarshalBlobTx(tx) + require.True(t, isBlobTx) + for blobIdx := range blobTx.Blobs { + shareRange, err := square.BlobShareRange(txs, pfbIdx, blobIdx, defaultMaxSquareSize, defaultSubtreeRootThreshold) + require.NoError(t, err) + require.LessOrEqual(t, shareRange.End, len(dataSquare)) + blobShares := dataSquare[shareRange.Start:shareRange.End] + blobSharesBytes, err := rawData(blobShares) + require.NoError(t, err) + require.True(t, bytes.Contains(blobSharesBytes, blobTx.Blobs[blobIdx].Data)) + } + } + + // error on out of bounds cases + _, err = square.BlobShareRange(txs, -1, 0, defaultMaxSquareSize, defaultSubtreeRootThreshold) + require.Error(t, err) + + _, err = square.BlobShareRange(txs, 0, -1, defaultMaxSquareSize, defaultSubtreeRootThreshold) + require.Error(t, err) + + _, err = square.BlobShareRange(txs, 10, 0, defaultMaxSquareSize, defaultSubtreeRootThreshold) + require.Error(t, err) + + _, err = square.BlobShareRange(txs, 0, 10, defaultMaxSquareSize, defaultSubtreeRootThreshold) + require.Error(t, err) +} + +func TestSquareDeconstruct(t *testing.T) { + t.Run("ConstructDeconstructParity", func(t *testing.T) { + // 8192 -> square size 128 + for _, numTxs := range []int{2, 128, 1024, 8192} { + t.Run(fmt.Sprintf("%d", numTxs), func(t *testing.T) { + txs := generateOrderedTxs(numTxs/2, numTxs/2, 1, 800) + dataSquare, err := square.Construct(txs, defaultMaxSquareSize, defaultSubtreeRootThreshold) + require.NoError(t, err) + recomputedTxs, err := square.Deconstruct(dataSquare, test.DecodeMockPFB) + require.NoError(t, err) + require.Equal(t, txs, recomputedTxs) + }) + } + }) + t.Run("NoPFBs", func(t *testing.T) { + const numTxs = 10 + txs := test.GenerateTxs(250, 250, numTxs) + dataSquare, err := square.Construct(txs, defaultMaxSquareSize, defaultSubtreeRootThreshold) + require.NoError(t, err) + recomputedTxs, err := square.Deconstruct(dataSquare, test.DecodeMockPFB) + require.NoError(t, err) + require.Equal(t, txs, recomputedTxs) + }) + t.Run("PFBsOnly", func(t *testing.T) { + txs := test.GenerateBlobTxs(100, 1, 1024) + dataSquare, err := square.Construct(txs, defaultMaxSquareSize, defaultSubtreeRootThreshold) + require.NoError(t, err) + recomputedTxs, err := square.Deconstruct(dataSquare, test.DecodeMockPFB) + require.NoError(t, err) + require.Equal(t, txs, recomputedTxs) + }) + t.Run("EmptySquare", func(t *testing.T) { + tx, err := square.Deconstruct(square.EmptySquare(), test.DecodeMockPFB) + require.NoError(t, err) + require.Equal(t, [][]byte{}, tx) + }) +} func TestSize(t *testing.T) { type test struct {