Skip to content

Commit

Permalink
test: implements deterministic random transaction generation function (
Browse files Browse the repository at this point in the history
…celestiaorg#1975)

## Overview
Closes celestiaorg#1942 

Some improvement ideas: 
1. This PR introduces a `rand` parameter to some of the test utilities,
using which will generate some randomness internally. The type of this
new parameter is currently fixed to the `Rand` type from the Tendermint
package. However, an alternative approach would be to define our own
Rand interface and change the `rand` parameter type (that has been added
to some of the test utils in this PR) to accept any type that satisfies
that interface. In this PR, I chose not to follow this approach because
I wasn't sure if it would be necessary. Additionally, considering the
current organization of the test utilities, introducing such an
interface would only increase the complexity of the code. Nevertheless,
I am open to hearing your thoughts on this matter.

2. There is significant room for improvement in the organization of the
test utilities (not related to this PR). For example, we are currently
using this
[function](https://github.com/celestiaorg/celestia-app/blob/8146ae75f255f7bb5a2c87f32e7823d506266a53/pkg/square/builder_test.go#L78)
from another test file for square fuzz testing. Ideally, such utilities
are better to be consolidated under one package, and all other packages
should utilize them. However, in order to maintain consistency with the
existing architecture, I refrained from introducing any changes related
to the code organization. As a result, I kept all the unit tests for the
test helpers next to their respective locations. We can actually have
another issue to discuss this aspect as well.

## Checklist

- [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
  • Loading branch information
staheri14 authored Jun 27, 2023
1 parent b02ad59 commit 4725fd8
Show file tree
Hide file tree
Showing 16 changed files with 251 additions and 77 deletions.
4 changes: 3 additions & 1 deletion app/test/check_tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"bytes"
"testing"

tmrand "github.com/tendermint/tendermint/libs/rand"

"github.com/celestiaorg/celestia-app/app"
"github.com/celestiaorg/celestia-app/app/encoding"
appns "github.com/celestiaorg/celestia-app/pkg/namespace"
Expand Down Expand Up @@ -100,7 +102,7 @@ func TestCheckTx(t *testing.T) {
name: "normal blobTx w/ multiple blobs, CheckTxType_New",
checkType: abci.CheckTxType_New,
getTx: func() []byte {
tx := blobfactory.RandBlobTxsWithAccounts(encCfg.TxConfig.TxEncoder(), kr, nil, 10000, 10, true, testutil.ChainID, accs[3:4])[0]
tx := blobfactory.RandBlobTxsWithAccounts(encCfg.TxConfig.TxEncoder(), tmrand.NewRand(), kr, nil, 10000, 10, true, testutil.ChainID, accs[3:4])[0]
return tx
},
expectedABCICode: abci.CodeTypeOK,
Expand Down
5 changes: 5 additions & 0 deletions app/test/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func (s *IntegrationTestSuite) TestMaxBlockSize() {
equallySized1MbTxGen := func(c client.Context) []coretypes.Tx {
return blobfactory.RandBlobTxsWithAccounts(
s.ecfg.TxConfig.TxEncoder(),
tmrand.NewRand(),
s.cctx.Keyring,
c.GRPCClient,
950000,
Expand All @@ -96,6 +97,7 @@ func (s *IntegrationTestSuite) TestMaxBlockSize() {
randMultiBlob1MbTxGen := func(c client.Context) []coretypes.Tx {
return blobfactory.RandBlobTxsWithAccounts(
s.ecfg.TxConfig.TxEncoder(),
tmrand.NewRand(),
s.cctx.Keyring,
c.GRPCClient,
200000, // 200 KiB
Expand All @@ -113,6 +115,7 @@ func (s *IntegrationTestSuite) TestMaxBlockSize() {
randoTxGen := func(c client.Context) []coretypes.Tx {
return blobfactory.RandBlobTxsWithAccounts(
s.ecfg.TxConfig.TxEncoder(),
tmrand.NewRand(),
s.cctx.Keyring,
c.GRPCClient,
50000,
Expand Down Expand Up @@ -270,6 +273,7 @@ func (s *IntegrationTestSuite) TestUnwrappedPFBRejection() {

blobTx := blobfactory.RandBlobTxsWithAccounts(
s.ecfg.TxConfig.TxEncoder(),
tmrand.NewRand(),
s.cctx.Keyring,
s.cctx.GRPCClient,
int(100000),
Expand All @@ -293,6 +297,7 @@ func (s *IntegrationTestSuite) TestShareInclusionProof() {
// generate 100 randomly sized txs (max size == 100kb)
txs := blobfactory.RandBlobTxsWithAccounts(
s.ecfg.TxConfig.TxEncoder(),
tmrand.NewRand(),
s.cctx.Keyring,
s.cctx.GRPCClient,
100000,
Expand Down
4 changes: 3 additions & 1 deletion app/test/prepare_proposal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package app_test
import (
"testing"

tmrand "github.com/tendermint/tendermint/libs/rand"

"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -87,7 +89,7 @@ func TestPrepareProposalFiltering(t *testing.T) {
infos[:3],
blobfactory.NestedBlobs(
t,
appns.RandomBlobNamespaces(3),
appns.RandomBlobNamespaces(tmrand.NewRand(), 3),
[][]int{{100}, {1000}, {420}},
),
)
Expand Down
4 changes: 2 additions & 2 deletions app/test/process_proposal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestProcessProposal(t *testing.T) {
t, enc, kr, testutil.ChainID, accounts[:4], infos[:4],
blobfactory.NestedBlobs(
t,
appns.RandomBlobNamespaces(4),
appns.RandomBlobNamespaces(tmrand.NewRand(), 4),
[][]int{{100}, {1000}, {420}, {300}},
),
)
Expand Down Expand Up @@ -206,7 +206,7 @@ func TestProcessProposal(t *testing.T) {
mutator: func(d *core.Data) {
encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...)
index := 4
tx, blob := blobfactory.IndexWrappedTxWithInvalidNamespace(t, encCfg.TxConfig.TxEncoder(), signer, 0, 0, uint32(index))
tx, blob := blobfactory.IndexWrappedTxWithInvalidNamespace(t, encCfg.TxConfig.TxEncoder(), tmrand.NewRand(), signer, 0, 0, uint32(index))
blobTx, err := coretypes.MarshalBlobTx(tx, &blob)
require.NoError(t, err)

Expand Down
18 changes: 14 additions & 4 deletions pkg/namespace/random_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,22 @@ import (
)

func RandomBlobNamespaceID() []byte {
return tmrand.Bytes(NamespaceVersionZeroIDSize)
return RandomBlobNamespaceIDWithPRG(tmrand.NewRand())
}

// RandomBlobNamespaceIDWithPRG returns a random blob namespace ID using the supplied Pseudo-Random number Generator (PRG).
func RandomBlobNamespaceIDWithPRG(prg *tmrand.Rand) []byte {
return prg.Bytes(NamespaceVersionZeroIDSize)
}

func RandomBlobNamespace() Namespace {
return RandomBlobNamespaceWithPRG(tmrand.NewRand())
}

// RandomBlobNamespaceWithPRG generates and returns a random blob namespace using the supplied Pseudo-Random number Generator (PRG).
func RandomBlobNamespaceWithPRG(prg *tmrand.Rand) Namespace {
for {
id := RandomBlobNamespaceID()
id := RandomBlobNamespaceIDWithPRG(prg)
namespace := MustNewV0(id)
err := namespace.ValidateBlobNamespace()
if err != nil {
Expand All @@ -20,9 +30,9 @@ func RandomBlobNamespace() Namespace {
}
}

func RandomBlobNamespaces(count int) (namespaces []Namespace) {
func RandomBlobNamespaces(rand *tmrand.Rand, count int) (namespaces []Namespace) {
for i := 0; i < count; i++ {
namespaces = append(namespaces, RandomBlobNamespace())
namespaces = append(namespaces, RandomBlobNamespaceWithPRG(rand))
}
return namespaces
}
4 changes: 3 additions & 1 deletion pkg/proof/proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"bytes"
"testing"

tmrand "github.com/tendermint/tendermint/libs/rand"

"github.com/celestiaorg/celestia-app/app"
"github.com/celestiaorg/celestia-app/app/encoding"
"github.com/celestiaorg/celestia-app/test/util/blobfactory"
Expand All @@ -23,7 +25,7 @@ import (
func TestNewTxInclusionProof(t *testing.T) {
blockTxs := testfactory.GenerateRandomTxs(50, 500).ToSliceOfBytes()
encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...)
blockTxs = append(blockTxs, blobfactory.RandBlobTxs(encCfg.TxConfig.TxEncoder(), 50, 1, 500).ToSliceOfBytes()...)
blockTxs = append(blockTxs, blobfactory.RandBlobTxs(encCfg.TxConfig.TxEncoder(), tmrand.NewRand(), 50, 1, 500).ToSliceOfBytes()...)
require.Len(t, blockTxs, 100)

type test struct {
Expand Down
66 changes: 53 additions & 13 deletions pkg/square/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package square_test
import (
"bytes"
"fmt"
"math/rand"
"testing"

"github.com/stretchr/testify/assert"

apptypes "github.com/celestiaorg/celestia-app/x/blob/types"

"github.com/cosmos/cosmos-sdk/client"

"github.com/celestiaorg/celestia-app/app"
Expand All @@ -17,6 +20,7 @@ import (
"github.com/celestiaorg/celestia-app/test/util/blobfactory"
"github.com/celestiaorg/celestia-app/test/util/testfactory"
"github.com/stretchr/testify/require"
tmrand "github.com/tendermint/tendermint/libs/rand"
"github.com/tendermint/tendermint/types"
core "github.com/tendermint/tendermint/types"
coretypes "github.com/tendermint/tendermint/types"
Expand Down Expand Up @@ -46,21 +50,22 @@ func TestBuilderSquareSizeEstimation(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
txs := generateMixedTxs(tt.normalTxs, tt.pfbCount, 1, tt.pfbSize)
rand := tmrand.NewRand()
txs := generateMixedTxs(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 generateMixedTxs(normalTxCount, pfbCount, blobsPerPfb, blobSize int) [][]byte {
return shuffle(generateOrderedTxs(normalTxCount, pfbCount, blobsPerPfb, blobSize))
func generateMixedTxs(rand *tmrand.Rand, normalTxCount, pfbCount, blobsPerPfb, blobSize int) [][]byte {
return shuffle(rand, generateOrderedTxs(rand, normalTxCount, pfbCount, blobsPerPfb, blobSize))
}

func generateOrderedTxs(normalTxCount, pfbCount, blobsPerPfb, blobSize int) [][]byte {
func generateOrderedTxs(rand *tmrand.Rand, normalTxCount, pfbCount, blobsPerPfb, blobSize int) [][]byte {
encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...)
pfbTxs := blobfactory.RandBlobTxs(encCfg.TxConfig.TxEncoder(), pfbCount, blobsPerPfb, blobSize)
pfbTxs := blobfactory.RandBlobTxs(encCfg.TxConfig.TxEncoder(), rand, pfbCount, blobsPerPfb, blobSize)
normieTxs := blobfactory.GenerateManyRawSendTxs(encCfg.TxConfig, normalTxCount)
txs := append(append(
make([]coretypes.Tx, 0, len(pfbTxs)+len(normieTxs)),
Expand All @@ -71,9 +76,10 @@ func generateOrderedTxs(normalTxCount, pfbCount, blobsPerPfb, blobSize int) [][]
}

// GenerateOrderedRandomTxs generates normalTxCount random Send transactions and pfbCount random MultiBlob transactions.
func GenerateOrderedRandomTxs(t *testing.T, txConfig client.TxConfig, normalTxCount, pfbCount int) [][]byte {
noramlTxs := blobfactory.GenerateManyRandomRawSendTxs(txConfig, normalTxCount)
pfbTxs := blobfactory.RandMultiBlobTxs(t, txConfig.TxEncoder(), pfbCount)
func GenerateOrderedRandomTxs(t *testing.T, txConfig client.TxConfig, rand *tmrand.Rand, normalTxCount, pfbCount int) [][]byte {
signer := apptypes.GenerateKeyringSigner(t)
noramlTxs := blobfactory.GenerateManyRandomRawSendTxsSameSigner(txConfig, rand, signer, normalTxCount)
pfbTxs := blobfactory.RandMultiBlobTxsSameSigner(t, txConfig.TxEncoder(), rand, signer, pfbCount)
txs := append(append(
make([]coretypes.Tx, 0, len(pfbTxs)+len(noramlTxs)),
noramlTxs...),
Expand All @@ -82,11 +88,45 @@ func GenerateOrderedRandomTxs(t *testing.T, txConfig client.TxConfig, normalTxCo
return coretypes.Txs(txs).ToSliceOfBytes()
}

func GenerateMixedRandomTxs(t *testing.T, txConfig client.TxConfig, normalTxCount, pfbCount int) [][]byte {
return shuffle(GenerateOrderedRandomTxs(t, txConfig, normalTxCount, pfbCount))
// TestGenerateOrderedRandomTxs_Deterministic ensures that the same seed produces the same txs
func TestGenerateOrderedRandomTxs_Deterministic(t *testing.T) {
pfbCount := 10
noramlCount := 10
encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...)

rand1 := tmrand.NewRand()
rand1.Seed(1)
set1 := GenerateOrderedRandomTxs(t, encCfg.TxConfig, rand1, noramlCount, pfbCount)

rand2 := tmrand.NewRand()
rand2.Seed(1)
set2 := GenerateOrderedRandomTxs(t, encCfg.TxConfig, rand2, noramlCount, pfbCount)

assert.Equal(t, set2, set1)
}

func GenerateMixedRandomTxs(t *testing.T, txConfig client.TxConfig, rand *tmrand.Rand, normalTxCount, pfbCount int) [][]byte {
return shuffle(rand, GenerateOrderedRandomTxs(t, txConfig, rand, normalTxCount, pfbCount))
}

// TestGenerateMixedRandomTxs_Deterministic ensures that the same seed produces the same txs
func TestGenerateMixedRandomTxs_Deterministic(t *testing.T) {
pfbCount := 10
noramlCount := 10
encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...)

rand1 := tmrand.NewRand()
rand1.Seed(1)
set1 := GenerateMixedRandomTxs(t, encCfg.TxConfig, rand1, noramlCount, pfbCount)

rand2 := tmrand.NewRand()
rand2.Seed(1)
set2 := GenerateMixedRandomTxs(t, encCfg.TxConfig, rand2, noramlCount, pfbCount)

assert.Equal(t, set2, set1)
}

func shuffle(slice [][]byte) [][]byte {
func shuffle(rand *tmrand.Rand, slice [][]byte) [][]byte {
for i := range slice {
j := rand.Intn(i + 1)
slice[i], slice[j] = slice[j], slice[i]
Expand Down Expand Up @@ -164,7 +204,7 @@ func newTx(len int) []byte {
func TestBuilderFindTxShareRange(t *testing.T) {
blockTxs := testfactory.GenerateRandomTxs(5, 900).ToSliceOfBytes()
encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...)
blockTxs = append(blockTxs, blobfactory.RandBlobTxsRandomlySized(encCfg.TxConfig.TxEncoder(), 5, 1000, 10).ToSliceOfBytes()...)
blockTxs = append(blockTxs, blobfactory.RandBlobTxsRandomlySized(encCfg.TxConfig.TxEncoder(), tmrand.NewRand(), 5, 1000, 10).ToSliceOfBytes()...)
require.Len(t, blockTxs, 10)

builder, err := square.NewBuilder(appconsts.DefaultSquareSizeUpperBound, appconsts.DefaultSubtreeRootThreshold, blockTxs...)
Expand Down
8 changes: 5 additions & 3 deletions pkg/square/square_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"testing"

tmrand "github.com/tendermint/tendermint/libs/rand"

"github.com/celestiaorg/celestia-app/pkg/appconsts"
"github.com/celestiaorg/celestia-app/pkg/square"
"github.com/stretchr/testify/require"
Expand All @@ -12,7 +14,7 @@ import (
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)
txs := generateOrderedTxs(tmrand.NewRand(), txCount/2, txCount/2, 1, 1024)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := square.Construct(txs, appconsts.LatestVersion, appconsts.DefaultSquareSizeUpperBound)
Expand All @@ -25,7 +27,7 @@ func BenchmarkSquareConstruct(b *testing.B) {
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)
txs := generateMixedTxs(tmrand.NewRand(), txCount/2, txCount/2, 1, 1024)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, err := square.Build(txs, appconsts.LatestVersion, appconsts.DefaultSquareSizeUpperBound)
Expand All @@ -36,7 +38,7 @@ func BenchmarkSquareBuild(b *testing.B) {
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)
txs := generateMixedTxs(tmrand.NewRand(), 0, txCount, 1, blobSize)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, err := square.Build(txs, appconsts.LatestVersion, appconsts.DefaultSquareSizeUpperBound)
Expand Down
10 changes: 7 additions & 3 deletions pkg/square/square_fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
blob "github.com/celestiaorg/celestia-app/x/blob/types"
"github.com/celestiaorg/rsmt2d"
"github.com/stretchr/testify/require"
tmrand "github.com/tendermint/tendermint/libs/rand"
)

// FuzzSquare uses fuzzing to test the following:
Expand All @@ -27,15 +28,18 @@ func FuzzSquare(f *testing.F) {
var (
normalTxCount = 12
pfbCount = 91
seed = int64(3554045230938829713)
)
f.Add(normalTxCount, pfbCount)
f.Fuzz(func(t *testing.T, normalTxCount, pfbCount int) {
f.Add(normalTxCount, pfbCount, seed)
f.Fuzz(func(t *testing.T, normalTxCount, pfbCount int, seed int64) {
// ignore invalid values
if normalTxCount < 0 || pfbCount < 0 {
t.Skip()
}
encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...)
txs := GenerateMixedRandomTxs(t, encCfg.TxConfig, normalTxCount, pfbCount)
rand := tmrand.NewRand()
rand.Seed(seed)
txs := GenerateMixedRandomTxs(t, encCfg.TxConfig, rand, normalTxCount, pfbCount)

s, orderedTxs, err := square.Build(txs, appconsts.LatestVersion, appconsts.DefaultSquareSizeUpperBound)
require.NoError(t, err)
Expand Down
Loading

0 comments on commit 4725fd8

Please sign in to comment.