github.com/celestiaorg/celestia-node@v0.15.0-beta.1/share/availability/full/reconstruction_test.go (about)

     1  //go:build !race
     2  
     3  package full
     4  
     5  import (
     6  	"context"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/require"
    12  	"golang.org/x/sync/errgroup"
    13  
    14  	"github.com/celestiaorg/celestia-node/header/headertest"
    15  	"github.com/celestiaorg/celestia-node/share"
    16  	"github.com/celestiaorg/celestia-node/share/availability/light"
    17  	availability_test "github.com/celestiaorg/celestia-node/share/availability/test"
    18  	"github.com/celestiaorg/celestia-node/share/eds"
    19  )
    20  
    21  func init() {
    22  	eds.RetrieveQuadrantTimeout = time.Millisecond * 100 // to speed up tests
    23  }
    24  
    25  // TestShareAvailable_OneFullNode asserts that a full node can ensure
    26  // data is available (reconstruct data square) while being connected to
    27  // light nodes only.
    28  func TestShareAvailable_OneFullNode(t *testing.T) {
    29  	// NOTE: Numbers are taken from the original 'Fraud and Data Availability Proofs' paper
    30  	light.DefaultSampleAmount = 20 // s
    31  	const (
    32  		origSquareSize = 16 // k
    33  		lightNodes     = 69 // c
    34  	)
    35  
    36  	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
    37  	defer cancel()
    38  
    39  	net := availability_test.NewTestDAGNet(ctx, t)
    40  	source, root := RandNode(net, origSquareSize) // make a source node, a.k.a bridge
    41  	eh := headertest.RandExtendedHeader(t)
    42  	eh.DAH = root
    43  	full := Node(net) // make a full availability service which reconstructs data
    44  
    45  	// ensure there is no connection between source and full nodes
    46  	// so that full reconstructs from the light nodes only
    47  	net.Disconnect(source.ID(), full.ID())
    48  
    49  	errg, errCtx := errgroup.WithContext(ctx)
    50  	errg.Go(func() error {
    51  		return full.SharesAvailable(errCtx, eh)
    52  	})
    53  
    54  	lights := make([]*availability_test.TestNode, lightNodes)
    55  	for i := 0; i < len(lights); i++ {
    56  		lights[i] = light.Node(net)
    57  		go func(i int) {
    58  			err := lights[i].SharesAvailable(ctx, eh)
    59  			if err != nil {
    60  				t.Log("light errors:", err)
    61  			}
    62  		}(i)
    63  	}
    64  
    65  	for i := 0; i < len(lights); i++ {
    66  		net.Connect(lights[i].ID(), source.ID())
    67  	}
    68  
    69  	for i := 0; i < len(lights); i++ {
    70  		net.Connect(lights[i].ID(), full.ID())
    71  	}
    72  
    73  	err := errg.Wait()
    74  	require.NoError(t, err)
    75  }
    76  
    77  // TestShareAvailable_ConnectedFullNodes asserts that two connected full nodes
    78  // can ensure data availability via two isolated light node subnetworks. Full
    79  // nodes start their availability process first, then light node start
    80  // availability process and connect to full node and only after light node
    81  // connect to the source node which has the data. After light node connect to the
    82  // source, full node must be able to finish the availability process started in
    83  // the beginning.
    84  func TestShareAvailable_ConnectedFullNodes(t *testing.T) {
    85  	// NOTE: Numbers are taken from the original 'Fraud and Data Availability Proofs' paper
    86  	light.DefaultSampleAmount = 20 // s
    87  	const (
    88  		origSquareSize = 16 // k
    89  		lightNodes     = 60 // c
    90  	)
    91  
    92  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
    93  	defer cancel()
    94  
    95  	net := availability_test.NewTestDAGNet(ctx, t)
    96  	source, root := RandNode(net, origSquareSize)
    97  	eh := headertest.RandExtendedHeader(t)
    98  	eh.DAH = root
    99  
   100  	// create two full nodes and ensure they are disconnected
   101  	full1 := Node(net)
   102  	full2 := Node(net)
   103  
   104  	// pre-connect fulls
   105  	net.Connect(full1.ID(), full2.ID())
   106  	// ensure fulls and source are not connected
   107  	// so that fulls take data from light nodes only
   108  	net.Disconnect(full1.ID(), source.ID())
   109  	net.Disconnect(full2.ID(), source.ID())
   110  
   111  	// start reconstruction for fulls
   112  	errg, errCtx := errgroup.WithContext(ctx)
   113  	errg.Go(func() error {
   114  		return full1.SharesAvailable(errCtx, eh)
   115  	})
   116  	errg.Go(func() error {
   117  		return full2.SharesAvailable(errCtx, eh)
   118  	})
   119  
   120  	// create light nodes and start sampling for them immediately
   121  	lights1, lights2 := make(
   122  		[]*availability_test.TestNode, lightNodes/2),
   123  		make([]*availability_test.TestNode, lightNodes/2)
   124  	for i := 0; i < len(lights1); i++ {
   125  		lights1[i] = light.Node(net)
   126  		go func(i int) {
   127  			err := lights1[i].SharesAvailable(ctx, eh)
   128  			if err != nil {
   129  				t.Log("light1 errors:", err)
   130  			}
   131  		}(i)
   132  
   133  		lights2[i] = light.Node(net)
   134  		go func(i int) {
   135  			err := lights2[i].SharesAvailable(ctx, eh)
   136  			if err != nil {
   137  				t.Log("light2 errors:", err)
   138  			}
   139  		}(i)
   140  	}
   141  
   142  	// shape topology
   143  	for i := 0; i < len(lights1); i++ {
   144  		// ensure lights1 are only connected to full1
   145  		net.Connect(lights1[i].ID(), full1.ID())
   146  		net.Disconnect(lights1[i].ID(), full2.ID())
   147  		// ensure lights2 are only connected to full2
   148  		net.Connect(lights2[i].ID(), full2.ID())
   149  		net.Disconnect(lights2[i].ID(), full1.ID())
   150  	}
   151  
   152  	// start connection lights with sources
   153  	for i := 0; i < len(lights1); i++ {
   154  		net.Connect(lights1[i].ID(), source.ID())
   155  		net.Connect(lights2[i].ID(), source.ID())
   156  	}
   157  
   158  	err := errg.Wait()
   159  	require.NoError(t, err)
   160  }
   161  
   162  // TestShareAvailable_DisconnectedFullNodes asserts that two disconnected full
   163  // nodes cannot ensure data is available (reconstruct data square) while being
   164  // connected to isolated light nodes subnetworks, which do not have enough nodes
   165  // to reconstruct the data, but once ShareAvailability nodes connect, they can
   166  // collectively reconstruct it.
   167  func TestShareAvailable_DisconnectedFullNodes(t *testing.T) {
   168  	// S - Source
   169  	// L - Light Node
   170  	// F - Full Node
   171  	// ── - connection
   172  	//
   173  	// Topology:
   174  	// NOTE: There are more Light Nodes in practice
   175  	// ┌─┬─┬─S─┬─┬─┐
   176  	// │ │ │   │ │ │
   177  	// │ │ │   │ │ │
   178  	// │ │ │   │ │ │
   179  	// L L L   L L L
   180  	// │ │ │   │ │ │
   181  	// └─┴─┤   ├─┴─┘
   182  	//    F└───┘F
   183  	//
   184  
   185  	// NOTE: Numbers are taken from the original 'Fraud and Data Availability Proofs' paper
   186  	light.DefaultSampleAmount = 20 // s
   187  	const (
   188  		origSquareSize = 16 // k
   189  		lightNodes     = 32 // c - total number of nodes on two subnetworks
   190  	)
   191  
   192  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*60)
   193  	defer cancel()
   194  
   195  	net := availability_test.NewTestDAGNet(ctx, t)
   196  	source, root := RandNode(net, origSquareSize)
   197  	eh := headertest.RandExtendedHeader(t)
   198  	eh.DAH = root
   199  
   200  	// create light nodes and start sampling for them immediately
   201  	lights1, lights2 := make(
   202  		[]*availability_test.TestNode, lightNodes/2),
   203  		make([]*availability_test.TestNode, lightNodes/2)
   204  
   205  	var wg sync.WaitGroup
   206  	wg.Add(lightNodes)
   207  	for i := 0; i < len(lights1); i++ {
   208  		lights1[i] = light.Node(net)
   209  		go func(i int) {
   210  			defer wg.Done()
   211  			err := lights1[i].SharesAvailable(ctx, eh)
   212  			if err != nil {
   213  				t.Log("light1 errors:", err)
   214  			}
   215  		}(i)
   216  
   217  		lights2[i] = light.Node(net)
   218  		go func(i int) {
   219  			defer wg.Done()
   220  			err := lights2[i].SharesAvailable(ctx, eh)
   221  			if err != nil {
   222  				t.Log("light2 errors:", err)
   223  			}
   224  		}(i)
   225  	}
   226  
   227  	// create two full nodes and ensure they are disconnected
   228  	full1 := Node(net)
   229  	full2 := Node(net)
   230  	net.Disconnect(full1.ID(), full2.ID())
   231  
   232  	// ensure fulls and source are not connected
   233  	// so that fulls take data from light nodes only
   234  	net.Disconnect(full1.ID(), source.ID())
   235  	net.Disconnect(full2.ID(), source.ID())
   236  
   237  	// shape topology
   238  	for i := 0; i < len(lights1); i++ {
   239  		// ensure lights1 are only connected to source and full1
   240  		net.Connect(lights1[i].ID(), source.ID())
   241  		net.Connect(lights1[i].ID(), full1.ID())
   242  		net.Disconnect(lights1[i].ID(), full2.ID())
   243  		// ensure lights2 are only connected to source and full2
   244  		net.Connect(lights2[i].ID(), source.ID())
   245  		net.Connect(lights2[i].ID(), full2.ID())
   246  		net.Disconnect(lights2[i].ID(), full1.ID())
   247  	}
   248  
   249  	// start reconstruction for fulls that should fail
   250  	ctxErr, cancelErr := context.WithTimeout(ctx, time.Second*5)
   251  	errg, errCtx := errgroup.WithContext(ctxErr)
   252  	errg.Go(func() error {
   253  		return full1.SharesAvailable(errCtx, eh)
   254  	})
   255  	errg.Go(func() error {
   256  		return full2.SharesAvailable(errCtx, eh)
   257  	})
   258  
   259  	// check that any of the fulls cannot reconstruct on their own
   260  	err := errg.Wait()
   261  	require.ErrorIs(t, err, share.ErrNotAvailable)
   262  	cancelErr()
   263  
   264  	// but after they connect
   265  	net.Connect(full1.ID(), full2.ID())
   266  
   267  	// with clean caches from the previous try
   268  	full1.ClearStorage()
   269  	full2.ClearStorage()
   270  
   271  	// they both should be able to reconstruct the block
   272  	errg, bctx := errgroup.WithContext(ctx)
   273  	errg.Go(func() error {
   274  		return full1.SharesAvailable(bctx, eh)
   275  	})
   276  	errg.Go(func() error {
   277  		return full2.SharesAvailable(bctx, eh)
   278  	})
   279  	require.NoError(t, errg.Wait())
   280  	// wait for all routines to finish before exit, in case there are any errors to log
   281  	wg.Wait()
   282  }