github.com/sunrise-zone/sunrise-node@v0.13.1-sr2/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 "github.com/cometbft/cometbft/libs/rand" 13 bstore "github.com/ipfs/boxo/blockstore" 14 ds "github.com/ipfs/go-datastore" 15 carv1 "github.com/ipld/go-car" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 19 "github.com/celestiaorg/rsmt2d" 20 "github.com/sunrise-zone/sunrise-app/pkg/appconsts" 21 "github.com/sunrise-zone/sunrise-app/pkg/da" 22 23 "github.com/sunrise-zone/sunrise-node/share" 24 "github.com/sunrise-zone/sunrise-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 }