diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index 9058a8eb05..8ba7308e64 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -5,14 +5,13 @@ import ( "errors" "fmt" - "github.com/filecoin-project/dagstore" logging "github.com/ipfs/go-log/v2" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/eds/byzantine" - "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/celestia-node/share/shwap" + "github.com/celestiaorg/celestia-node/store" ) var log = logging.Logger("share/full") @@ -21,14 +20,14 @@ var log = logging.Logger("share/full") // recovery technique. It is considered "full" because it is required // to download enough shares to fully reconstruct the data square. type ShareAvailability struct { - store *eds.Store - getter share.Getter + store *store.Store + getter shwap.Getter } // NewShareAvailability creates a new full ShareAvailability. func NewShareAvailability( - store *eds.Store, - getter share.Getter, + store *store.Store, + getter shwap.Getter, ) *ShareAvailability { return &ShareAvailability{ store: store, @@ -54,14 +53,10 @@ func (fa *ShareAvailability) SharesAvailable(ctx context.Context, header *header } // a hack to avoid loading the whole EDS in mem if we store it already. - if ok, _ := fa.store.Has(ctx, dah.Hash()); ok { + if ok, _ := fa.store.HasByHeight(ctx, header.Height()); ok { return nil } - adder := ipld.NewProofsAdder(len(dah.RowRoots), false) - ctx = ipld.CtxWithProofsAdder(ctx, adder) - defer adder.Purge() - eds, err := fa.getter.GetEDS(ctx, header) if err != nil { if errors.Is(err, context.Canceled) { @@ -75,8 +70,8 @@ func (fa *ShareAvailability) SharesAvailable(ctx context.Context, header *header return err } - err = fa.store.Put(ctx, dah.Hash(), eds) - if err != nil && !errors.Is(err, dagstore.ErrShardExists) { + err = fa.store.Put(ctx, dah, header.Height(), eds) + if err != nil { return fmt.Errorf("full availability: failed to store eds: %w", err) } return nil diff --git a/share/availability/full/availability_test.go b/share/availability/full/availability_test.go index 03e4a46769..b0ea6e9468 100644 --- a/share/availability/full/availability_test.go +++ b/share/availability/full/availability_test.go @@ -3,73 +3,87 @@ package full import ( "context" "testing" + "time" "github.com/golang/mock/gomock" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" - availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/eds/edstest" - "github.com/celestiaorg/celestia-node/share/mocks" + "github.com/celestiaorg/celestia-node/share/shwap/getters/mock" + "github.com/celestiaorg/celestia-node/store" ) -func TestShareAvailableOverMocknet_Full(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) +func TestSharesAvailable(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - net := availability_test.NewTestDAGNet(ctx, t) - _, root := RandNode(net, 32) - - eh := headertest.RandExtendedHeaderWithRoot(t, root) - nd := Node(net) - net.ConnectAll() + // RandServiceWithSquare creates a NewShareAvailability inside, so we can test it + eds := edstest.RandEDS(t, 16) + roots, err := share.NewAxisRoots(eds) + eh := headertest.RandExtendedHeaderWithRoot(t, roots) - err := nd.SharesAvailable(ctx, eh) - assert.NoError(t, err) -} + getter := mock.NewMockGetter(gomock.NewController(t)) + getter.EXPECT().GetEDS(gomock.Any(), eh).Return(eds, nil) -func TestSharesAvailable_Full(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + store, err := store.NewStore(store.DefaultParameters(), t.TempDir()) + avail := NewShareAvailability(store, getter) + err = avail.SharesAvailable(ctx, eh) + require.NoError(t, err) - // RandServiceWithSquare creates a NewShareAvailability inside, so we can test it - getter, roots := GetterWithRandSquare(t, 16) + // Check if the store has the root + has, err := store.HasByHash(ctx, roots.Hash()) + require.NoError(t, err) + require.True(t, has) - eh := headertest.RandExtendedHeaderWithRoot(t, roots) - avail := TestAvailability(t, getter) - err := avail.SharesAvailable(ctx, eh) - assert.NoError(t, err) + // Check if the store has the root linked to the height + has, err = store.HasByHeight(ctx, eh.Height()) + require.NoError(t, err) + require.True(t, has) } -func TestSharesAvailable_StoresToEDSStore(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) +func TestSharesAvailable_StoredEds(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - // RandServiceWithSquare creates a NewShareAvailability inside, so we can test it - getter, roots := GetterWithRandSquare(t, 16) + eds := edstest.RandEDS(t, 4) + roots, err := share.NewAxisRoots(eds) eh := headertest.RandExtendedHeaderWithRoot(t, roots) - avail := TestAvailability(t, getter) - err := avail.SharesAvailable(ctx, eh) - assert.NoError(t, err) + require.NoError(t, err) + + store, err := store.NewStore(store.DefaultParameters(), t.TempDir()) + avail := NewShareAvailability(store, nil) + + prevHeigh := eh.Height() - 1 + err = store.Put(ctx, roots, prevHeigh, eds) + require.NoError(t, err) - has, err := avail.store.Has(ctx, roots.Hash()) - assert.NoError(t, err) - assert.True(t, has) + has, err := store.HasByHeight(ctx, eh.Height()) + require.NoError(t, err) + require.False(t, has) + + err = avail.SharesAvailable(ctx, eh) + require.NoError(t, err) + + has, err = store.HasByHeight(ctx, eh.Height()) + require.NoError(t, err) + require.True(t, has) } -func TestSharesAvailable_Full_ErrNotAvailable(t *testing.T) { +func TestSharesAvailable_ErrNotAvailable(t *testing.T) { ctrl := gomock.NewController(t) - getter := mocks.NewMockGetter(ctrl) - ctx, cancel := context.WithCancel(context.Background()) + getter := mock.NewMockGetter(ctrl) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() eds := edstest.RandEDS(t, 4) roots, err := share.NewAxisRoots(eds) eh := headertest.RandExtendedHeaderWithRoot(t, roots) require.NoError(t, err) - avail := TestAvailability(t, getter) + + store, err := store.NewStore(store.DefaultParameters(), t.TempDir()) + avail := NewShareAvailability(store, getter) errors := []error{share.ErrNotFound, context.DeadlineExceeded} for _, getterErr := range errors { diff --git a/share/availability/full/reconstruction_test.go b/share/availability/full/reconstruction_test.go deleted file mode 100644 index 31edb3b6d9..0000000000 --- a/share/availability/full/reconstruction_test.go +++ /dev/null @@ -1,284 +0,0 @@ -//go:build !race - -package full - -import ( - "context" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/require" - "golang.org/x/sync/errgroup" - - "github.com/celestiaorg/celestia-node/header/headertest" - "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/availability/light" - availability_test "github.com/celestiaorg/celestia-node/share/availability/test" - "github.com/celestiaorg/celestia-node/share/eds" -) - -func init() { - eds.RetrieveQuadrantTimeout = time.Millisecond * 100 // to speed up tests -} - -// TestShareAvailable_OneFullNode asserts that a full node can ensure -// data is available (reconstruct data square) while being connected to -// light nodes only. -func TestShareAvailable_OneFullNode(t *testing.T) { - // NOTE: Numbers are taken from the original 'Fraud and Data Availability Proofs' paper - light.DefaultSampleAmount = 20 // s - const ( - origSquareSize = 16 // k - lightNodes = 69 // c - ) - - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) - defer cancel() - - net := availability_test.NewTestDAGNet(ctx, t) - source, root := RandNode(net, origSquareSize) // make a source node, a.k.a bridge - eh := headertest.RandExtendedHeader(t) - eh.DAH = root - full := Node(net) // make a full availability service which reconstructs data - - // ensure there is no connection between source and full nodes - // so that full reconstructs from the light nodes only - net.Disconnect(source.ID(), full.ID()) - - errg, errCtx := errgroup.WithContext(ctx) - errg.Go(func() error { - return full.SharesAvailable(errCtx, eh) - }) - - lights := make([]*availability_test.TestNode, lightNodes) - for i := 0; i < len(lights); i++ { - lights[i] = light.Node(net) - go func(i int) { - err := lights[i].SharesAvailable(ctx, eh) - if err != nil { - t.Log("light errors:", err) - } - }(i) - } - - for i := 0; i < len(lights); i++ { - net.Connect(lights[i].ID(), source.ID()) - } - - for i := 0; i < len(lights); i++ { - net.Connect(lights[i].ID(), full.ID()) - } - - err := errg.Wait() - require.NoError(t, err) -} - -// TestShareAvailable_ConnectedFullNodes asserts that two connected full nodes -// can ensure data availability via two isolated light node subnetworks. Full -// nodes start their availability process first, then light node start -// availability process and connect to full node and only after light node -// connect to the source node which has the data. After light node connect to the -// source, full node must be able to finish the availability process started in -// the beginning. -func TestShareAvailable_ConnectedFullNodes(t *testing.T) { - // NOTE: Numbers are taken from the original 'Fraud and Data Availability Proofs' paper - light.DefaultSampleAmount = 20 // s - const ( - origSquareSize = 16 // k - lightNodes = 60 // c - ) - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - - net := availability_test.NewTestDAGNet(ctx, t) - source, root := RandNode(net, origSquareSize) - eh := headertest.RandExtendedHeader(t) - eh.DAH = root - - // create two full nodes and ensure they are disconnected - full1 := Node(net) - full2 := Node(net) - - // pre-connect fulls - net.Connect(full1.ID(), full2.ID()) - // ensure fulls and source are not connected - // so that fulls take data from light nodes only - net.Disconnect(full1.ID(), source.ID()) - net.Disconnect(full2.ID(), source.ID()) - - // start reconstruction for fulls - errg, errCtx := errgroup.WithContext(ctx) - errg.Go(func() error { - return full1.SharesAvailable(errCtx, eh) - }) - errg.Go(func() error { - return full2.SharesAvailable(errCtx, eh) - }) - - // create light nodes and start sampling for them immediately - lights1, lights2 := make( - []*availability_test.TestNode, lightNodes/2), - make([]*availability_test.TestNode, lightNodes/2) - for i := 0; i < len(lights1); i++ { - lights1[i] = light.Node(net) - go func(i int) { - err := lights1[i].SharesAvailable(ctx, eh) - if err != nil { - t.Log("light1 errors:", err) - } - }(i) - - lights2[i] = light.Node(net) - go func(i int) { - err := lights2[i].SharesAvailable(ctx, eh) - if err != nil { - t.Log("light2 errors:", err) - } - }(i) - } - - // shape topology - for i := 0; i < len(lights1); i++ { - // ensure lights1 are only connected to full1 - net.Connect(lights1[i].ID(), full1.ID()) - net.Disconnect(lights1[i].ID(), full2.ID()) - // ensure lights2 are only connected to full2 - net.Connect(lights2[i].ID(), full2.ID()) - net.Disconnect(lights2[i].ID(), full1.ID()) - } - - // start connection lights with sources - for i := 0; i < len(lights1); i++ { - net.Connect(lights1[i].ID(), source.ID()) - net.Connect(lights2[i].ID(), source.ID()) - } - - err := errg.Wait() - require.NoError(t, err) -} - -// TestShareAvailable_DisconnectedFullNodes asserts that two disconnected full -// nodes cannot ensure data is available (reconstruct data square) while being -// connected to isolated light nodes subnetworks, which do not have enough nodes -// to reconstruct the data, but once ShareAvailability nodes connect, they can -// collectively reconstruct it. -// -//nolint:dupword -func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { - // S - Source - // L - Light Node - // F - Full Node - // ── - connection - // - // Topology: - // NOTE: There are more Light Nodes in practice - // ┌─┬─┬─S─┬─┬─┐ - // │ │ │ │ │ │ - // │ │ │ │ │ │ - // │ │ │ │ │ │ - // L L L L L L - // │ │ │ │ │ │ - // └─┴─┤ ├─┴─┘ - // F└───┘F - // - - // NOTE: Numbers are taken from the original 'Fraud and Data Availability Proofs' paper - light.DefaultSampleAmount = 20 // s - const ( - origSquareSize = 16 // k - lightNodes = 32 // c - total number of nodes on two subnetworks - ) - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) - defer cancel() - - net := availability_test.NewTestDAGNet(ctx, t) - source, root := RandNode(net, origSquareSize) - eh := headertest.RandExtendedHeader(t) - eh.DAH = root - - // create light nodes and start sampling for them immediately - lights1, lights2 := make( - []*availability_test.TestNode, lightNodes/2), - make([]*availability_test.TestNode, lightNodes/2) - - var wg sync.WaitGroup - wg.Add(lightNodes) - for i := 0; i < len(lights1); i++ { - lights1[i] = light.Node(net) - go func(i int) { - defer wg.Done() - err := lights1[i].SharesAvailable(ctx, eh) - if err != nil { - t.Log("light1 errors:", err) - } - }(i) - - lights2[i] = light.Node(net) - go func(i int) { - defer wg.Done() - err := lights2[i].SharesAvailable(ctx, eh) - if err != nil { - t.Log("light2 errors:", err) - } - }(i) - } - - // create two full nodes and ensure they are disconnected - full1 := Node(net) - full2 := Node(net) - net.Disconnect(full1.ID(), full2.ID()) - - // ensure fulls and source are not connected - // so that fulls take data from light nodes only - net.Disconnect(full1.ID(), source.ID()) - net.Disconnect(full2.ID(), source.ID()) - - // shape topology - for i := 0; i < len(lights1); i++ { - // ensure lights1 are only connected to source and full1 - net.Connect(lights1[i].ID(), source.ID()) - net.Connect(lights1[i].ID(), full1.ID()) - net.Disconnect(lights1[i].ID(), full2.ID()) - // ensure lights2 are only connected to source and full2 - net.Connect(lights2[i].ID(), source.ID()) - net.Connect(lights2[i].ID(), full2.ID()) - net.Disconnect(lights2[i].ID(), full1.ID()) - } - - // start reconstruction for fulls that should fail - ctxErr, cancelErr := context.WithTimeout(ctx, time.Second*5) - errg, errCtx := errgroup.WithContext(ctxErr) - errg.Go(func() error { - return full1.SharesAvailable(errCtx, eh) - }) - errg.Go(func() error { - return full2.SharesAvailable(errCtx, eh) - }) - - // check that any of the fulls cannot reconstruct on their own - err := errg.Wait() - require.ErrorIs(t, err, share.ErrNotAvailable) - cancelErr() - - // but after they connect - net.Connect(full1.ID(), full2.ID()) - - // with clean caches from the previous try - full1.ClearStorage() - full2.ClearStorage() - - // they both should be able to reconstruct the block - errg, bctx := errgroup.WithContext(ctx) - errg.Go(func() error { - return full1.SharesAvailable(bctx, eh) - }) - errg.Go(func() error { - return full2.SharesAvailable(bctx, eh) - }) - require.NoError(t, errg.Wait()) - // wait for all routines to finish before exit, in case there are any errors to log - wg.Wait() -} diff --git a/share/availability/full/testing.go b/share/availability/full/testing.go deleted file mode 100644 index ca35398075..0000000000 --- a/share/availability/full/testing.go +++ /dev/null @@ -1,56 +0,0 @@ -package full - -import ( - "context" - "testing" - "time" - - "github.com/ipfs/go-datastore" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/celestia-node/share" - availability_test "github.com/celestiaorg/celestia-node/share/availability/test" - "github.com/celestiaorg/celestia-node/share/eds" - "github.com/celestiaorg/celestia-node/share/getters" - "github.com/celestiaorg/celestia-node/share/ipld" - "github.com/celestiaorg/celestia-node/share/p2p/discovery" -) - -// GetterWithRandSquare provides a share.Getter filled with 'n' NMT -// trees of 'n' random shares, essentially storing a whole square. -func GetterWithRandSquare(t *testing.T, n int) (share.Getter, *share.AxisRoots) { - bServ := ipld.NewMemBlockservice() - getter := getters.NewIPLDGetter(bServ) - return getter, availability_test.RandFillBS(t, n, bServ) -} - -// RandNode creates a Full Node filled with a random block of the given size. -func RandNode(dn *availability_test.TestDagNet, squareSize int) (*availability_test.TestNode, *share.AxisRoots) { - nd := Node(dn) - return nd, availability_test.RandFillBS(dn.T, squareSize, nd.BlockService) -} - -// Node creates a new empty Full Node. -func Node(dn *availability_test.TestDagNet) *availability_test.TestNode { - nd := dn.NewTestNode() - nd.Getter = getters.NewIPLDGetter(nd.BlockService) - nd.Availability = TestAvailability(dn.T, nd.Getter) - return nd -} - -func TestAvailability(t *testing.T, getter share.Getter) *ShareAvailability { - params := discovery.DefaultParameters() - params.AdvertiseInterval = time.Second - params.PeersLimit = 10 - - store, err := eds.NewStore(eds.DefaultParameters(), t.TempDir(), datastore.NewMapDatastore()) - require.NoError(t, err) - err = store.Start(context.Background()) - require.NoError(t, err) - - t.Cleanup(func() { - err = store.Stop(context.Background()) - require.NoError(t, err) - }) - return NewShareAvailability(store, getter) -} diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index c4300a29f2..ff9a50c1cc 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -13,6 +13,7 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/getters" + "github.com/celestiaorg/celestia-node/share/shwap" ) var ( @@ -26,7 +27,7 @@ var ( // its availability. It is assumed that there are a lot of lightAvailability instances // on the network doing sampling over the same Root to collectively verify its availability. type ShareAvailability struct { - getter share.Getter + getter shwap.Getter params Parameters // TODO(@Wondertan): Once we come to parallelized DASer, this lock becomes a contention point @@ -38,7 +39,7 @@ type ShareAvailability struct { // NewShareAvailability creates a new light Availability. func NewShareAvailability( - getter share.Getter, + getter shwap.Getter, ds datastore.Batching, opts ...Option, ) *ShareAvailability { diff --git a/share/availability/light/availability_test.go b/share/availability/light/availability_test.go index 07fbe28b34..6f969e2173 100644 --- a/share/availability/light/availability_test.go +++ b/share/availability/light/availability_test.go @@ -3,28 +3,46 @@ package light import ( "context" _ "embed" - "strconv" + "sync" "testing" + "github.com/golang/mock/gomock" + "github.com/ipfs/go-datastore" "github.com/stretchr/testify/require" + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" - availability_test "github.com/celestiaorg/celestia-node/share/availability/test" - "github.com/celestiaorg/celestia-node/share/ipld" - "github.com/celestiaorg/celestia-node/share/sharetest" + "github.com/celestiaorg/celestia-node/share/eds/edstest" + "github.com/celestiaorg/celestia-node/share/shwap" + "github.com/celestiaorg/celestia-node/share/shwap/getters/mock" ) func TestSharesAvailableCaches(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - getter, eh := GetterWithRandSquare(t, 16) - dah := eh.DAH - avail := TestAvailability(getter) + eds := edstest.RandEDS(t, 16) + roots, err := share.NewAxisRoots(eds) + require.NoError(t, err) + eh := headertest.RandExtendedHeaderWithRoot(t, roots) + + getter := mock.NewMockGetter(gomock.NewController(t)) + getter.EXPECT(). + GetShare(gomock.Any(), eh, gomock.Any(), gomock.Any()). + DoAndReturn( + func(_ context.Context, _ *header.ExtendedHeader, row, col int) (share.Share, error) { + return eds.GetCell(uint(row), uint(col)), nil + }). + AnyTimes() + + ds := datastore.NewMapDatastore() + avail := NewShareAvailability(getter, ds) // cache doesn't have eds yet - has, err := avail.ds.Has(ctx, rootKey(dah)) + has, err := avail.ds.Has(ctx, rootKey(roots)) require.NoError(t, err) require.False(t, has) @@ -32,7 +50,7 @@ func TestSharesAvailableCaches(t *testing.T) { require.NoError(t, err) // is now stored success result - result, err := avail.ds.Get(ctx, rootKey(dah)) + result, err := avail.ds.Get(ctx, rootKey(roots)) require.NoError(t, err) failed, err := decodeSamples(result) require.NoError(t, err) @@ -43,15 +61,21 @@ func TestSharesAvailableHitsCache(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - getter, _ := GetterWithRandSquare(t, 16) - avail := TestAvailability(getter) + // create getter that always return ErrNotFound + getter := mock.NewMockGetter(gomock.NewController(t)) + getter.EXPECT(). + GetShare(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil, share.ErrNotFound). + AnyTimes() + + ds := datastore.NewMapDatastore() + avail := NewShareAvailability(getter, ds) - // create new roots, that is not available by getter - bServ := ipld.NewMemBlockservice() - roots := availability_test.RandFillBS(t, 16, bServ) + // generate random header + roots := edstest.RandomAxisRoots(t, 16) eh := headertest.RandExtendedHeaderWithRoot(t, roots) - // blockstore doesn't actually have the eds + // store doesn't actually have the eds err := avail.SharesAvailable(ctx, eh) require.ErrorIs(t, err, share.ErrNotAvailable) @@ -68,9 +92,11 @@ func TestSharesAvailableEmptyRoot(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - getter, _ := GetterWithRandSquare(t, 16) - avail := TestAvailability(getter) + getter := mock.NewMockGetter(gomock.NewController(t)) + ds := datastore.NewMapDatastore() + avail := NewShareAvailability(getter, ds) + // request for empty eds eh := headertest.RandExtendedHeaderWithRoot(t, share.EmptyEDSRoots()) err := avail.SharesAvailable(ctx, eh) require.NoError(t, err) @@ -80,16 +106,22 @@ func TestSharesAvailableFailed(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - getter, _ := GetterWithRandSquare(t, 16) - avail := TestAvailability(getter) + getter := mock.NewMockGetter(gomock.NewController(t)) + ds := datastore.NewMapDatastore() + avail := NewShareAvailability(getter, ds) // create new eds, that is not available by getter - bServ := ipld.NewMemBlockservice() - roots := availability_test.RandFillBS(t, 16, bServ) + eds := edstest.RandEDS(t, 16) + roots, err := share.NewAxisRoots(eds) + require.NoError(t, err) eh := headertest.RandExtendedHeaderWithRoot(t, roots) - // blockstore doesn't actually have the eds, so it should fail - err := avail.SharesAvailable(ctx, eh) + // getter doesn't have the eds, so it should fail + getter.EXPECT(). + GetShare(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil, share.ErrNotFound). + AnyTimes() + err = avail.SharesAvailable(ctx, eh) require.ErrorIs(t, err, share.ErrNotAvailable) // cache should have failed results now @@ -116,142 +148,45 @@ func TestSharesAvailableFailed(t *testing.T) { require.Empty(t, onceGetter.available) } -func TestShareAvailableOverMocknet_Light(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - net := availability_test.NewTestDAGNet(ctx, t) - _, root := RandNode(net, 16) - eh := headertest.RandExtendedHeader(t) - eh.DAH = root - nd := Node(net) - net.ConnectAll() - - err := nd.SharesAvailable(ctx, eh) - require.NoError(t, err) +type onceGetter struct { + *sync.Mutex + available map[Sample]struct{} } -func TestGetShare(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - n := 16 - getter, eh := GetterWithRandSquare(t, n) - - for i := range make([]bool, n) { - for j := range make([]bool, n) { - sh, err := getter.GetShare(ctx, eh, i, j) - require.NotNil(t, sh) - require.NoError(t, err) - } +func newOnceGetter() onceGetter { + return onceGetter{ + Mutex: &sync.Mutex{}, + available: make(map[Sample]struct{}), } } -func TestService_GetSharesByNamespace(t *testing.T) { - tests := []struct { - squareSize int - expectedShareCount int - }{ - {squareSize: 4, expectedShareCount: 2}, - {squareSize: 16, expectedShareCount: 2}, - {squareSize: 128, expectedShareCount: 2}, - } - - for _, tt := range tests { - t.Run("size: "+strconv.Itoa(tt.squareSize), func(t *testing.T) { - getter, bServ := EmptyGetter() - totalShares := tt.squareSize * tt.squareSize - randShares := sharetest.RandShares(t, totalShares) - idx1 := (totalShares - 1) / 2 - idx2 := totalShares / 2 - if tt.expectedShareCount > 1 { - // make it so that two rows have the same namespace - copy(share.GetNamespace(randShares[idx2]), share.GetNamespace(randShares[idx1])) - } - root := availability_test.FillBS(t, bServ, randShares) - eh := headertest.RandExtendedHeader(t) - eh.DAH = root - randNamespace := share.GetNamespace(randShares[idx1]) - - shares, err := getter.GetSharesByNamespace(context.Background(), eh, randNamespace) - require.NoError(t, err) - require.NoError(t, shares.Verify(root, randNamespace)) - flattened := shares.Flatten() - require.Len(t, flattened, tt.expectedShareCount) - for _, value := range flattened { - require.Equal(t, randNamespace, share.GetNamespace(value)) - } - if tt.expectedShareCount > 1 { - // idx1 is always smaller than idx2 - require.Equal(t, randShares[idx1], flattened[0]) - require.Equal(t, randShares[idx2], flattened[1]) - } - }) - t.Run("last two rows of a 4x4 square that have the same namespace have valid NMT proofs", func(t *testing.T) { - squareSize := 4 - totalShares := squareSize * squareSize - getter, bServ := EmptyGetter() - randShares := sharetest.RandShares(t, totalShares) - lastNID := share.GetNamespace(randShares[totalShares-1]) - for i := totalShares / 2; i < totalShares; i++ { - copy(share.GetNamespace(randShares[i]), lastNID) - } - root := availability_test.FillBS(t, bServ, randShares) - eh := headertest.RandExtendedHeader(t) - eh.DAH = root - - shares, err := getter.GetSharesByNamespace(context.Background(), eh, lastNID) - require.NoError(t, err) - require.NoError(t, shares.Verify(root, lastNID)) - }) +func (m onceGetter) AddSamples(samples []Sample) { + m.Lock() + defer m.Unlock() + for _, s := range samples { + m.available[s] = struct{}{} } } -func TestGetShares(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - n := 16 - getter, eh := GetterWithRandSquare(t, n) - - eds, err := getter.GetEDS(ctx, eh) - require.NoError(t, err) - gotRoots, err := share.NewAxisRoots(eds) - require.NoError(t, err) - - require.True(t, eh.DAH.Equals(gotRoots)) +func (m onceGetter) GetShare(_ context.Context, _ *header.ExtendedHeader, row, col int) (share.Share, error) { + m.Lock() + defer m.Unlock() + s := Sample{Row: uint16(row), Col: uint16(col)} + if _, ok := m.available[s]; ok { + delete(m.available, s) + return share.Share{}, nil + } + return share.Share{}, share.ErrNotAvailable } -func TestService_GetSharesByNamespaceNotFound(t *testing.T) { - getter, eh := GetterWithRandSquare(t, 1) - eh.DAH.RowRoots = nil - - emptyShares, err := getter.GetSharesByNamespace(context.Background(), eh, sharetest.RandV0Namespace()) - require.NoError(t, err) - require.Empty(t, emptyShares.Flatten()) +func (m onceGetter) GetEDS(_ context.Context, _ *header.ExtendedHeader) (*rsmt2d.ExtendedDataSquare, error) { + panic("not implemented") } -func BenchmarkService_GetSharesByNamespace(b *testing.B) { - tests := []struct { - amountShares int - }{ - {amountShares: 4}, - {amountShares: 16}, - {amountShares: 128}, - } - - for _, tt := range tests { - b.Run(strconv.Itoa(tt.amountShares), func(b *testing.B) { - t := &testing.T{} - getter, eh := GetterWithRandSquare(t, tt.amountShares) - root := eh.DAH - randNamespace := root.RowRoots[(len(root.RowRoots)-1)/2][:share.NamespaceSize] - root.RowRoots[(len(root.RowRoots) / 2)] = root.RowRoots[(len(root.RowRoots)-1)/2] - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := getter.GetSharesByNamespace(context.Background(), eh, randNamespace) - require.NoError(t, err) - } - }) - } +func (m onceGetter) GetSharesByNamespace( + _ context.Context, + _ *header.ExtendedHeader, + _ share.Namespace, +) (shwap.NamespacedData, error) { + panic("not implemented") } diff --git a/share/availability/light/testing.go b/share/availability/light/testing.go deleted file mode 100644 index 82874f7d1b..0000000000 --- a/share/availability/light/testing.go +++ /dev/null @@ -1,107 +0,0 @@ -package light - -import ( - "context" - "sync" - "testing" - - "github.com/ipfs/boxo/blockservice" - "github.com/ipfs/go-datastore" - - "github.com/celestiaorg/rsmt2d" - - "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/header/headertest" - "github.com/celestiaorg/celestia-node/share" - availability_test "github.com/celestiaorg/celestia-node/share/availability/test" - "github.com/celestiaorg/celestia-node/share/getters" - "github.com/celestiaorg/celestia-node/share/ipld" -) - -// GetterWithRandSquare provides a share.Getter filled with 'n' NMT trees of 'n' random shares, -// essentially storing a whole square. -func GetterWithRandSquare(t *testing.T, n int) (share.Getter, *header.ExtendedHeader) { - bServ := ipld.NewMemBlockservice() - getter := getters.NewIPLDGetter(bServ) - root := availability_test.RandFillBS(t, n, bServ) - eh := headertest.RandExtendedHeader(t) - eh.DAH = root - - return getter, eh -} - -// EmptyGetter provides an unfilled share.Getter with corresponding blockservice.BlockService than -// can be filled by the test. -func EmptyGetter() (share.Getter, blockservice.BlockService) { - bServ := ipld.NewMemBlockservice() - getter := getters.NewIPLDGetter(bServ) - return getter, bServ -} - -// RandNode creates a Light Node filled with a random block of the given size. -func RandNode(dn *availability_test.TestDagNet, squareSize int) (*availability_test.TestNode, *share.AxisRoots) { - nd := Node(dn) - return nd, availability_test.RandFillBS(dn.T, squareSize, nd.BlockService) -} - -// Node creates a new empty Light Node. -func Node(dn *availability_test.TestDagNet) *availability_test.TestNode { - nd := dn.NewTestNode() - nd.Getter = getters.NewIPLDGetter(nd.BlockService) - nd.Availability = TestAvailability(nd.Getter) - return nd -} - -func TestAvailability(getter share.Getter) *ShareAvailability { - ds := datastore.NewMapDatastore() - return NewShareAvailability(getter, ds) -} - -func SubNetNode(sn *availability_test.SubNet) *availability_test.TestNode { - nd := Node(sn.TestDagNet) - sn.AddNode(nd) - return nd -} - -type onceGetter struct { - *sync.Mutex - available map[Sample]struct{} -} - -func newOnceGetter() onceGetter { - return onceGetter{ - Mutex: &sync.Mutex{}, - available: make(map[Sample]struct{}), - } -} - -func (m onceGetter) AddSamples(samples []Sample) { - m.Lock() - defer m.Unlock() - for _, s := range samples { - m.available[s] = struct{}{} - } -} - -func (m onceGetter) GetShare(_ context.Context, _ *header.ExtendedHeader, row, col int) (share.Share, error) { - m.Lock() - defer m.Unlock() - s := Sample{Row: uint16(row), Col: uint16(col)} - if _, ok := m.available[s]; ok { - delete(m.available, s) - return share.Share{}, nil - } - return share.Share{}, share.ErrNotAvailable -} - -func (m onceGetter) GetEDS(_ context.Context, _ *header.ExtendedHeader) (*rsmt2d.ExtendedDataSquare, error) { - panic("not implemented") -} - -func (m onceGetter) GetSharesByNamespace( - _ context.Context, - _ *header.ExtendedHeader, - _ share.Namespace, -) (share.NamespacedShares, error) { - panic("not implemented") -}