github.com/MetalBlockchain/metalgo@v1.11.9/tests/e2e/faultinjection/duplicate_node_id.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package faultinjection
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/MetalBlockchain/metalgo/api/info"
    13  	"github.com/MetalBlockchain/metalgo/config"
    14  	"github.com/MetalBlockchain/metalgo/ids"
    15  	"github.com/MetalBlockchain/metalgo/tests/fixture/e2e"
    16  	"github.com/MetalBlockchain/metalgo/tests/fixture/tmpnet"
    17  	"github.com/MetalBlockchain/metalgo/utils/set"
    18  
    19  	ginkgo "github.com/onsi/ginkgo/v2"
    20  )
    21  
    22  var _ = ginkgo.Describe("Duplicate node handling", func() {
    23  	require := require.New(ginkgo.GinkgoT())
    24  
    25  	ginkgo.It("should ensure that a given Node ID (i.e. staking keypair) can be used at most once on a network", func() {
    26  		network := e2e.Env.GetNetwork()
    27  
    28  		ginkgo.By("creating new node")
    29  		node1 := e2e.AddEphemeralNode(network, tmpnet.FlagsMap{})
    30  		e2e.WaitForHealthy(node1)
    31  
    32  		ginkgo.By("checking that the new node is connected to its peers")
    33  		checkConnectedPeers(network.Nodes, node1)
    34  
    35  		ginkgo.By("creating a second new node with the same staking keypair as the first new node")
    36  		node1Flags := node1.Flags
    37  		node2Flags := tmpnet.FlagsMap{
    38  			config.StakingTLSKeyContentKey: node1Flags[config.StakingTLSKeyContentKey],
    39  			config.StakingCertContentKey:   node1Flags[config.StakingCertContentKey],
    40  			// Construct a unique data dir to ensure the two nodes' data will be stored
    41  			// separately. Usually the dir name is the node ID but in this one case the nodes have
    42  			// the same node ID.
    43  			config.DataDirKey: fmt.Sprintf("%s-second", node1Flags[config.DataDirKey]),
    44  		}
    45  		node2 := e2e.AddEphemeralNode(network, node2Flags)
    46  
    47  		ginkgo.By("checking that the second new node fails to become healthy before timeout")
    48  		err := tmpnet.WaitForHealthy(e2e.DefaultContext(), node2)
    49  		require.ErrorIs(err, context.DeadlineExceeded)
    50  
    51  		ginkgo.By("stopping the first new node")
    52  		require.NoError(node1.Stop(e2e.DefaultContext()))
    53  
    54  		ginkgo.By("checking that the second new node becomes healthy within timeout")
    55  		e2e.WaitForHealthy(node2)
    56  
    57  		ginkgo.By("checking that the second new node is connected to its peers")
    58  		checkConnectedPeers(network.Nodes, node2)
    59  
    60  		// A bootstrap check was already performed by the second node.
    61  	})
    62  })
    63  
    64  // Check that a new node is connected to existing nodes and vice versa
    65  func checkConnectedPeers(existingNodes []*tmpnet.Node, newNode *tmpnet.Node) {
    66  	require := require.New(ginkgo.GinkgoT())
    67  
    68  	// Collect the node ids of the new node's peers
    69  	infoClient := info.NewClient(newNode.URI)
    70  	peers, err := infoClient.Peers(e2e.DefaultContext())
    71  	require.NoError(err)
    72  	peerIDs := set.NewSet[ids.NodeID](len(existingNodes))
    73  	for _, peer := range peers {
    74  		peerIDs.Add(peer.ID)
    75  	}
    76  
    77  	for _, existingNode := range existingNodes {
    78  		// Check that the existing node is a peer of the new node
    79  		require.True(peerIDs.Contains(existingNode.NodeID))
    80  
    81  		// Check that the new node is a peer
    82  		infoClient := info.NewClient(existingNode.URI)
    83  		peers, err := infoClient.Peers(e2e.DefaultContext())
    84  		require.NoError(err)
    85  		isPeer := false
    86  		for _, peer := range peers {
    87  			if peer.ID == newNode.NodeID {
    88  				isPeer = true
    89  				break
    90  			}
    91  		}
    92  		require.True(isPeer)
    93  	}
    94  }