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 }