github.com/celestiaorg/celestia-node@v0.15.0-beta.1/nodebuilder/tests/swamp/swamp.go (about) 1 package swamp 2 3 import ( 4 "context" 5 "crypto/rand" 6 "fmt" 7 "net" 8 "sync" 9 "testing" 10 "time" 11 12 ds "github.com/ipfs/go-datastore" 13 ds_sync "github.com/ipfs/go-datastore/sync" 14 "github.com/libp2p/go-libp2p/core/host" 15 "github.com/libp2p/go-libp2p/core/peer" 16 mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" 17 ma "github.com/multiformats/go-multiaddr" 18 "github.com/stretchr/testify/require" 19 "github.com/tendermint/tendermint/privval" 20 "github.com/tendermint/tendermint/types" 21 "go.uber.org/fx" 22 "golang.org/x/exp/maps" 23 24 "github.com/celestiaorg/celestia-app/test/util/testnode" 25 apptypes "github.com/celestiaorg/celestia-app/x/blob/types" 26 libhead "github.com/celestiaorg/go-header" 27 28 "github.com/celestiaorg/celestia-node/core" 29 "github.com/celestiaorg/celestia-node/header" 30 "github.com/celestiaorg/celestia-node/libs/keystore" 31 "github.com/celestiaorg/celestia-node/logs" 32 "github.com/celestiaorg/celestia-node/nodebuilder" 33 coremodule "github.com/celestiaorg/celestia-node/nodebuilder/core" 34 "github.com/celestiaorg/celestia-node/nodebuilder/node" 35 "github.com/celestiaorg/celestia-node/nodebuilder/p2p" 36 "github.com/celestiaorg/celestia-node/nodebuilder/state" 37 "github.com/celestiaorg/celestia-node/share/eds" 38 ) 39 40 var blackholeIP6 = net.ParseIP("100::") 41 42 // DefaultTestTimeout should be used as the default timeout on all the Swamp tests. 43 // It's generously set to 5 minutes to give enough time for CI. 44 const DefaultTestTimeout = time.Minute * 5 45 46 // Swamp represents the main functionality that is needed for the test-case: 47 // - Network to link the nodes 48 // - CoreClient to share between Bridge nodes 49 // - Slices of created Bridge/Full/Light Nodes 50 // - trustedHash taken from the CoreClient and shared between nodes 51 type Swamp struct { 52 t *testing.T 53 cfg *testnode.Config 54 55 Network mocknet.Mocknet 56 Bootstrappers []ma.Multiaddr 57 58 ClientContext testnode.Context 59 Accounts []string 60 61 nodesMu sync.Mutex 62 nodes map[*nodebuilder.Node]struct{} 63 64 genesis *header.ExtendedHeader 65 } 66 67 // NewSwamp creates a new instance of Swamp. 68 func NewSwamp(t *testing.T, options ...Option) *Swamp { 69 if testing.Verbose() { 70 logs.SetDebugLogging() 71 } 72 73 ic := DefaultConfig() 74 for _, option := range options { 75 option(ic) 76 } 77 78 // Now, we are making an assumption that consensus mechanism is already tested out 79 // so, we are not creating bridge nodes with each one containing its own core client 80 // instead we are assigning all created BNs to 1 Core from the swamp 81 ic.WithChainID("private") 82 cctx := core.StartTestNodeWithConfig(t, ic) 83 swp := &Swamp{ 84 t: t, 85 cfg: ic, 86 Network: mocknet.New(), 87 ClientContext: cctx, 88 Accounts: ic.Accounts, 89 nodes: map[*nodebuilder.Node]struct{}{}, 90 } 91 92 swp.t.Cleanup(swp.cleanup) 93 swp.setupGenesis() 94 return swp 95 } 96 97 // cleanup frees up all the resources 98 // including stop of all created nodes 99 func (s *Swamp) cleanup() { 100 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 101 defer cancel() 102 103 require.NoError(s.t, s.Network.Close()) 104 105 s.nodesMu.Lock() 106 defer s.nodesMu.Unlock() 107 maps.DeleteFunc(s.nodes, func(nd *nodebuilder.Node, _ struct{}) bool { 108 require.NoError(s.t, nd.Stop(ctx)) 109 return true 110 }) 111 } 112 113 // GetCoreBlockHashByHeight returns a tendermint block's hash by provided height 114 func (s *Swamp) GetCoreBlockHashByHeight(ctx context.Context, height int64) libhead.Hash { 115 b, err := s.ClientContext.Client.Block(ctx, &height) 116 require.NoError(s.t, err) 117 return libhead.Hash(b.BlockID.Hash) 118 } 119 120 // WaitTillHeight holds the test execution until the given amount of blocks 121 // has been produced by the CoreClient. 122 func (s *Swamp) WaitTillHeight(ctx context.Context, height int64) libhead.Hash { 123 require.Greater(s.t, height, int64(0)) 124 125 t := time.NewTicker(time.Millisecond * 50) 126 defer t.Stop() 127 for { 128 select { 129 case <-ctx.Done(): 130 require.NoError(s.t, ctx.Err()) 131 case <-t.C: 132 latest, err := s.ClientContext.LatestHeight() 133 require.NoError(s.t, err) 134 if latest >= height { 135 res, err := s.ClientContext.Client.Block(ctx, &latest) 136 require.NoError(s.t, err) 137 return libhead.Hash(res.BlockID.Hash) 138 } 139 } 140 } 141 } 142 143 // createPeer is a helper for celestia nodes to initialize 144 // with a real key instead of using a bogus one. 145 func (s *Swamp) createPeer(ks keystore.Keystore) host.Host { 146 key, err := p2p.Key(ks) 147 require.NoError(s.t, err) 148 149 // IPv6 will be starting with 100:0 150 token := make([]byte, 12) 151 _, _ = rand.Read(token) 152 ip := append(net.IP{}, blackholeIP6...) 153 copy(ip[net.IPv6len-len(token):], token) 154 155 // reference to GenPeer func in libp2p/p2p/net/mock/mock_net.go 156 // on how we generate new multiaddr for new peer 157 a, err := ma.NewMultiaddr(fmt.Sprintf("/ip6/%s/tcp/4242", ip)) 158 require.NoError(s.t, err) 159 160 host, err := s.Network.AddPeer(key, a) 161 require.NoError(s.t, err) 162 163 require.NoError(s.t, s.Network.LinkAll()) 164 return host 165 } 166 167 // setupGenesis sets up genesis Header. 168 // This is required to initialize and start correctly. 169 func (s *Swamp) setupGenesis() { 170 ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 171 defer cancel() 172 173 // ensure core has surpassed genesis block 174 s.WaitTillHeight(ctx, 2) 175 176 ds := ds_sync.MutexWrap(ds.NewMapDatastore()) 177 store, err := eds.NewStore(eds.DefaultParameters(), s.t.TempDir(), ds) 178 require.NoError(s.t, err) 179 180 ex, err := core.NewExchange( 181 core.NewBlockFetcher(s.ClientContext.Client), 182 store, 183 header.MakeExtendedHeader, 184 ) 185 require.NoError(s.t, err) 186 187 h, err := ex.GetByHeight(ctx, 1) 188 require.NoError(s.t, err) 189 s.genesis = h 190 } 191 192 // DefaultTestConfig creates a test config with the access to the core node for the tp 193 func (s *Swamp) DefaultTestConfig(tp node.Type) *nodebuilder.Config { 194 cfg := nodebuilder.DefaultConfig(tp) 195 196 ip, port, err := net.SplitHostPort(s.cfg.AppConfig.GRPC.Address) 197 require.NoError(s.t, err) 198 199 cfg.Core.IP = ip 200 cfg.Core.GRPCPort = port 201 return cfg 202 } 203 204 // NewBridgeNode creates a new instance of a BridgeNode providing a default config 205 // and a mockstore to the NewNodeWithStore method 206 func (s *Swamp) NewBridgeNode(options ...fx.Option) *nodebuilder.Node { 207 cfg := s.DefaultTestConfig(node.Bridge) 208 store := nodebuilder.MockStore(s.t, cfg) 209 210 return s.NewNodeWithStore(node.Bridge, store, options...) 211 } 212 213 // NewFullNode creates a new instance of a FullNode providing a default config 214 // and a mockstore to the NewNodeWithStore method 215 func (s *Swamp) NewFullNode(options ...fx.Option) *nodebuilder.Node { 216 cfg := s.DefaultTestConfig(node.Full) 217 cfg.Header.TrustedPeers = []string{ 218 "/ip4/1.2.3.4/tcp/12345/p2p/12D3KooWNaJ1y1Yio3fFJEXCZyd1Cat3jmrPdgkYCrHfKD3Ce21p", 219 } 220 // add all bootstrappers in suite as trusted peers 221 for _, bootstrapper := range s.Bootstrappers { 222 cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, bootstrapper.String()) 223 } 224 store := nodebuilder.MockStore(s.t, cfg) 225 226 return s.NewNodeWithStore(node.Full, store, options...) 227 } 228 229 // NewLightNode creates a new instance of a LightNode providing a default config 230 // and a mockstore to the NewNodeWithStore method 231 func (s *Swamp) NewLightNode(options ...fx.Option) *nodebuilder.Node { 232 cfg := s.DefaultTestConfig(node.Light) 233 cfg.Header.TrustedPeers = []string{ 234 "/ip4/1.2.3.4/tcp/12345/p2p/12D3KooWNaJ1y1Yio3fFJEXCZyd1Cat3jmrPdgkYCrHfKD3Ce21p", 235 } 236 // add all bootstrappers in suite as trusted peers 237 for _, bootstrapper := range s.Bootstrappers { 238 cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, bootstrapper.String()) 239 } 240 241 store := nodebuilder.MockStore(s.t, cfg) 242 243 return s.NewNodeWithStore(node.Light, store, options...) 244 } 245 246 func (s *Swamp) NewNodeWithConfig(nodeType node.Type, cfg *nodebuilder.Config, options ...fx.Option) *nodebuilder.Node { 247 store := nodebuilder.MockStore(s.t, cfg) 248 // add all bootstrappers in suite as trusted peers 249 for _, bootstrapper := range s.Bootstrappers { 250 cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, bootstrapper.String()) 251 } 252 return s.NewNodeWithStore(nodeType, store, options...) 253 } 254 255 // NewNodeWithStore creates a new instance of Node with predefined Store. 256 func (s *Swamp) NewNodeWithStore( 257 tp node.Type, 258 store nodebuilder.Store, 259 options ...fx.Option, 260 ) *nodebuilder.Node { 261 signer := apptypes.NewKeyringSigner(s.ClientContext.Keyring, s.Accounts[0], s.ClientContext.ChainID) 262 options = append(options, 263 state.WithKeyringSigner(signer), 264 ) 265 266 switch tp { 267 case node.Bridge: 268 options = append(options, 269 coremodule.WithClient(s.ClientContext.Client), 270 ) 271 default: 272 } 273 274 nd := s.newNode(tp, store, options...) 275 s.nodesMu.Lock() 276 s.nodes[nd] = struct{}{} 277 s.nodesMu.Unlock() 278 return nd 279 } 280 281 func (s *Swamp) newNode(t node.Type, store nodebuilder.Store, options ...fx.Option) *nodebuilder.Node { 282 ks, err := store.Keystore() 283 require.NoError(s.t, err) 284 285 // TODO(@Bidon15): If for some reason, we receive one of existing options 286 // like <core, host, hash> from the test case, we need to check them and not use 287 // default that are set here 288 cfg, _ := store.Config() 289 cfg.RPC.Port = "0" 290 291 // tempDir is used for the eds.Store 292 tempDir := s.t.TempDir() 293 options = append(options, 294 p2p.WithHost(s.createPeer(ks)), 295 fx.Replace(node.StorePath(tempDir)), 296 fx.Invoke(func(ctx context.Context, store libhead.Store[*header.ExtendedHeader]) error { 297 return store.Init(ctx, s.genesis) 298 }), 299 ) 300 node, err := nodebuilder.New(t, p2p.Private, store, options...) 301 require.NoError(s.t, err) 302 return node 303 } 304 305 // StopNode stops the node and removes from Swamp. 306 // TODO(@Wondertan): For clean and symmetrical API, we may want to add StartNode. 307 func (s *Swamp) StopNode(ctx context.Context, nd *nodebuilder.Node) { 308 s.nodesMu.Lock() 309 delete(s.nodes, nd) 310 s.nodesMu.Unlock() 311 require.NoError(s.t, nd.Stop(ctx)) 312 } 313 314 // Connect allows to connect peers after hard disconnection. 315 func (s *Swamp) Connect(t *testing.T, peerA, peerB *nodebuilder.Node) { 316 _, err := s.Network.LinkPeers(peerA.Host.ID(), peerB.Host.ID()) 317 require.NoError(t, err) 318 _, err = s.Network.ConnectPeers(peerA.Host.ID(), peerB.Host.ID()) 319 require.NoError(t, err) 320 } 321 322 // Disconnect allows to break a connection between two peers without any possibility to 323 // re-establish it. Order is very important here. We have to unlink peers first, and only after 324 // that call disconnect. This is hard disconnect and peers will not be able to reconnect. 325 // In order to reconnect peers again, please use swamp.Connect 326 func (s *Swamp) Disconnect(t *testing.T, peerA, peerB *nodebuilder.Node) { 327 require.NoError(t, s.Network.UnlinkPeers(peerA.Host.ID(), peerB.Host.ID())) 328 require.NoError(t, s.Network.DisconnectPeers(peerA.Host.ID(), peerB.Host.ID())) 329 } 330 331 // SetBootstrapper sets the given bootstrappers as the "bootstrappers" for the 332 // Swamp test suite. Every new full or light node created on the suite afterwards 333 // will automatically add the suite's bootstrappers as trusted peers to their config. 334 // NOTE: Bridge nodes do not automatically add the bootstrappers as trusted peers. 335 // NOTE: Use `NewNodeWithStore` to avoid this automatic configuration. 336 func (s *Swamp) SetBootstrapper(t *testing.T, bootstrappers ...*nodebuilder.Node) { 337 for _, trusted := range bootstrappers { 338 addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(trusted.Host)) 339 require.NoError(t, err) 340 s.Bootstrappers = append(s.Bootstrappers, addrs[0]) 341 } 342 } 343 344 // Validators retrieves keys from the app node in order to build the validators. 345 func (s *Swamp) Validators(t *testing.T) (*types.ValidatorSet, types.PrivValidator) { 346 privPath := s.cfg.TmConfig.PrivValidatorKeyFile() 347 statePath := s.cfg.TmConfig.PrivValidatorStateFile() 348 priv := privval.LoadFilePV(privPath, statePath) 349 key, err := priv.GetPubKey() 350 require.NoError(t, err) 351 validator := types.NewValidator(key, 100) 352 set := types.NewValidatorSet([]*types.Validator{validator}) 353 return set, priv 354 }