github.com/celestiaorg/celestia-node@v0.15.0-beta.1/share/eds/eds_test.go (about)

     1  package eds
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"embed"
     7  	"encoding/json"
     8  	"fmt"
     9  	"os"
    10  	"testing"
    11  
    12  	bstore "github.com/ipfs/boxo/blockstore"
    13  	ds "github.com/ipfs/go-datastore"
    14  	carv1 "github.com/ipld/go-car"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  	"github.com/tendermint/tendermint/libs/rand"
    18  
    19  	"github.com/celestiaorg/celestia-app/pkg/appconsts"
    20  	"github.com/celestiaorg/celestia-app/pkg/da"
    21  	"github.com/celestiaorg/rsmt2d"
    22  
    23  	"github.com/celestiaorg/celestia-node/share"
    24  	"github.com/celestiaorg/celestia-node/share/eds/edstest"
    25  )
    26  
    27  //go:embed "testdata/example-root.json"
    28  var exampleRoot string
    29  
    30  //go:embed "testdata/example.car"
    31  var f embed.FS
    32  
    33  func TestQuadrantOrder(t *testing.T) {
    34  	testCases := []struct {
    35  		name       string
    36  		squareSize int
    37  	}{
    38  		{"smol", 2},
    39  		{"still smol", 8},
    40  		{"default mainnet", appconsts.DefaultGovMaxSquareSize},
    41  		{"max", share.MaxSquareSize},
    42  	}
    43  
    44  	testShareSize := 64
    45  
    46  	for _, tc := range testCases {
    47  		t.Run(tc.name, func(t *testing.T) {
    48  			shares := make([][]byte, tc.squareSize*tc.squareSize)
    49  
    50  			for i := 0; i < tc.squareSize*tc.squareSize; i++ {
    51  				shares[i] = rand.Bytes(testShareSize)
    52  			}
    53  
    54  			eds, err := rsmt2d.ComputeExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), rsmt2d.NewDefaultTree)
    55  			require.NoError(t, err)
    56  
    57  			res := quadrantOrder(eds)
    58  			for _, s := range res {
    59  				require.Len(t, s, testShareSize+share.NamespaceSize)
    60  			}
    61  
    62  			for q := 0; q < 4; q++ {
    63  				for i := 0; i < tc.squareSize; i++ {
    64  					for j := 0; j < tc.squareSize; j++ {
    65  						resIndex := q*tc.squareSize*tc.squareSize + i*tc.squareSize + j
    66  						edsRow := q/2*tc.squareSize + i
    67  						edsCol := (q%2)*tc.squareSize + j
    68  
    69  						assert.Equal(t, res[resIndex], prependNamespace(q, eds.Row(uint(edsRow))[edsCol]))
    70  					}
    71  				}
    72  			}
    73  		})
    74  	}
    75  }
    76  
    77  func TestWriteEDS(t *testing.T) {
    78  	writeRandomEDS(t)
    79  }
    80  
    81  func TestWriteEDSHeaderRoots(t *testing.T) {
    82  	eds := writeRandomEDS(t)
    83  	f := openWrittenEDS(t)
    84  	defer f.Close()
    85  
    86  	reader, err := carv1.NewCarReader(f)
    87  	require.NoError(t, err, "error creating car reader")
    88  	roots, err := rootsToCids(eds)
    89  	require.NoError(t, err, "error converting roots to cids")
    90  	require.Equal(t, roots, reader.Header.Roots)
    91  }
    92  
    93  func TestWriteEDSStartsWithLeaves(t *testing.T) {
    94  	eds := writeRandomEDS(t)
    95  	f := openWrittenEDS(t)
    96  	defer f.Close()
    97  
    98  	reader, err := carv1.NewCarReader(f)
    99  	require.NoError(t, err, "error creating car reader")
   100  	block, err := reader.Next()
   101  	require.NoError(t, err, "error getting first block")
   102  
   103  	require.Equal(t, share.GetData(block.RawData()), eds.GetCell(0, 0))
   104  }
   105  
   106  func TestWriteEDSIncludesRoots(t *testing.T) {
   107  	writeRandomEDS(t)
   108  	f := openWrittenEDS(t)
   109  	defer f.Close()
   110  
   111  	bs := bstore.NewBlockstore(ds.NewMapDatastore())
   112  	ctx, cancel := context.WithCancel(context.Background())
   113  	t.Cleanup(cancel)
   114  	loaded, err := carv1.LoadCar(ctx, bs, f)
   115  	require.NoError(t, err, "error loading car file")
   116  	for _, root := range loaded.Roots {
   117  		ok, err := bs.Has(context.Background(), root)
   118  		require.NoError(t, err, "error checking if blockstore has root")
   119  		require.True(t, ok, "blockstore does not have root")
   120  	}
   121  }
   122  
   123  func TestWriteEDSInQuadrantOrder(t *testing.T) {
   124  	eds := writeRandomEDS(t)
   125  	f := openWrittenEDS(t)
   126  	defer f.Close()
   127  
   128  	reader, err := carv1.NewCarReader(f)
   129  	require.NoError(t, err, "error creating car reader")
   130  
   131  	shares := quadrantOrder(eds)
   132  	for i := 0; i < len(shares); i++ {
   133  		block, err := reader.Next()
   134  		require.NoError(t, err, "error getting block")
   135  		require.Equal(t, block.RawData(), shares[i])
   136  	}
   137  }
   138  
   139  func TestReadWriteRoundtrip(t *testing.T) {
   140  	eds := writeRandomEDS(t)
   141  	dah, err := share.NewRoot(eds)
   142  	require.NoError(t, err)
   143  	f := openWrittenEDS(t)
   144  	defer f.Close()
   145  
   146  	loaded, err := ReadEDS(context.Background(), f, dah.Hash())
   147  	require.NoError(t, err, "error reading EDS from file")
   148  
   149  	rowRoots, err := eds.RowRoots()
   150  	require.NoError(t, err)
   151  	loadedRowRoots, err := loaded.RowRoots()
   152  	require.NoError(t, err)
   153  	require.Equal(t, rowRoots, loadedRowRoots)
   154  
   155  	colRoots, err := eds.ColRoots()
   156  	require.NoError(t, err)
   157  	loadedColRoots, err := loaded.ColRoots()
   158  	require.NoError(t, err)
   159  	require.Equal(t, colRoots, loadedColRoots)
   160  }
   161  
   162  func TestReadEDS(t *testing.T) {
   163  	f, err := f.Open("testdata/example.car")
   164  	require.NoError(t, err, "error opening file")
   165  
   166  	var dah da.DataAvailabilityHeader
   167  	err = json.Unmarshal([]byte(exampleRoot), &dah)
   168  	require.NoError(t, err, "error unmarshaling example root")
   169  
   170  	loaded, err := ReadEDS(context.Background(), f, dah.Hash())
   171  	require.NoError(t, err, "error reading EDS from file")
   172  	rowRoots, err := loaded.RowRoots()
   173  	require.NoError(t, err)
   174  	require.Equal(t, dah.RowRoots, rowRoots)
   175  	colRoots, err := loaded.ColRoots()
   176  	require.NoError(t, err)
   177  	require.Equal(t, dah.ColumnRoots, colRoots)
   178  }
   179  
   180  func TestReadEDSContentIntegrityMismatch(t *testing.T) {
   181  	writeRandomEDS(t)
   182  	dah, err := da.NewDataAvailabilityHeader(edstest.RandEDS(t, 4))
   183  	require.NoError(t, err)
   184  	f := openWrittenEDS(t)
   185  	defer f.Close()
   186  
   187  	_, err = ReadEDS(context.Background(), f, dah.Hash())
   188  	require.ErrorContains(t, err, "share: content integrity mismatch: imported root")
   189  }
   190  
   191  // BenchmarkReadWriteEDS benchmarks the time it takes to write and read an EDS from disk. The
   192  // benchmark is run with a 4x4 ODS to a 64x64 ODS - a higher value can be used, but it will run for
   193  // much longer.
   194  func BenchmarkReadWriteEDS(b *testing.B) {
   195  	ctx, cancel := context.WithCancel(context.Background())
   196  	b.Cleanup(cancel)
   197  	for originalDataWidth := 4; originalDataWidth <= 64; originalDataWidth *= 2 {
   198  		eds := edstest.RandEDS(b, originalDataWidth)
   199  		dah, err := share.NewRoot(eds)
   200  		require.NoError(b, err)
   201  		b.Run(fmt.Sprintf("Writing %dx%d", originalDataWidth, originalDataWidth), func(b *testing.B) {
   202  			b.ReportAllocs()
   203  			for i := 0; i < b.N; i++ {
   204  				f := new(bytes.Buffer)
   205  				err := WriteEDS(ctx, eds, f)
   206  				require.NoError(b, err)
   207  			}
   208  		})
   209  		b.Run(fmt.Sprintf("Reading %dx%d", originalDataWidth, originalDataWidth), func(b *testing.B) {
   210  			b.ReportAllocs()
   211  			for i := 0; i < b.N; i++ {
   212  				b.StopTimer()
   213  				f := new(bytes.Buffer)
   214  				_ = WriteEDS(ctx, eds, f)
   215  				b.StartTimer()
   216  				_, err := ReadEDS(ctx, f, dah.Hash())
   217  				require.NoError(b, err)
   218  			}
   219  		})
   220  	}
   221  }
   222  
   223  func writeRandomEDS(t *testing.T) *rsmt2d.ExtendedDataSquare {
   224  	t.Helper()
   225  	ctx, cancel := context.WithCancel(context.Background())
   226  	t.Cleanup(cancel)
   227  	tmpDir := t.TempDir()
   228  	err := os.Chdir(tmpDir)
   229  	require.NoError(t, err, "error changing to the temporary test directory")
   230  	f, err := os.OpenFile("test.car", os.O_WRONLY|os.O_CREATE, 0600)
   231  	require.NoError(t, err, "error opening file")
   232  
   233  	eds := edstest.RandEDS(t, 4)
   234  	err = WriteEDS(ctx, eds, f)
   235  	require.NoError(t, err, "error writing EDS to file")
   236  	f.Close()
   237  	return eds
   238  }
   239  
   240  func openWrittenEDS(t *testing.T) *os.File {
   241  	t.Helper()
   242  	f, err := os.OpenFile("test.car", os.O_RDONLY, 0600)
   243  	require.NoError(t, err, "error opening file")
   244  	return f
   245  }
   246  
   247  /*
   248  use this function as needed to create new test data.
   249  
   250  example:
   251  
   252  	func Test_CreateData(t *testing.T) {
   253  		createTestData(t, "celestia-node/share/eds/testdata")
   254  	}
   255  */
   256  func createTestData(t *testing.T, testDir string) { //nolint:unused
   257  	t.Helper()
   258  	ctx, cancel := context.WithCancel(context.Background())
   259  	t.Cleanup(cancel)
   260  	err := os.Chdir(testDir)
   261  	require.NoError(t, err, "changing to the directory")
   262  	os.RemoveAll("example.car")
   263  	require.NoError(t, err, "removing old file")
   264  	f, err := os.OpenFile("example.car", os.O_WRONLY|os.O_CREATE, 0600)
   265  	require.NoError(t, err, "opening file")
   266  
   267  	eds := edstest.RandEDS(t, 4)
   268  	err = WriteEDS(ctx, eds, f)
   269  	require.NoError(t, err, "writing EDS to file")
   270  	f.Close()
   271  	dah, err := share.NewRoot(eds)
   272  	require.NoError(t, err)
   273  
   274  	header, err := json.MarshalIndent(dah, "", "")
   275  	require.NoError(t, err, "marshaling example root")
   276  	os.RemoveAll("example-root.json")
   277  	require.NoError(t, err, "removing old file")
   278  	f, err = os.OpenFile("example-root.json", os.O_WRONLY|os.O_CREATE, 0600)
   279  	require.NoError(t, err, "opening file")
   280  	_, err = f.Write(header)
   281  	require.NoError(t, err, "writing example root to file")
   282  	f.Close()
   283  }