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  }