github.com/MetalBlockchain/metalgo@v1.11.9/snow/consensus/snowball/network_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package snowball
     5  
     6  import (
     7  	"github.com/MetalBlockchain/metalgo/ids"
     8  	"github.com/MetalBlockchain/metalgo/utils/bag"
     9  	"github.com/MetalBlockchain/metalgo/utils/sampler"
    10  )
    11  
    12  type newConsensusFunc func(factory Factory, params Parameters, choice ids.ID) Consensus
    13  
    14  type Network struct {
    15  	params         Parameters
    16  	colors         []ids.ID
    17  	rngSource      sampler.Source
    18  	nodes, running []Consensus
    19  	factory        Factory
    20  }
    21  
    22  // Create a new network with [numColors] different possible colors to finalize.
    23  func NewNetwork(factory Factory, params Parameters, numColors int, rngSource sampler.Source) *Network {
    24  	n := &Network{
    25  		params:    params,
    26  		rngSource: rngSource,
    27  		factory:   factory,
    28  	}
    29  	for i := 0; i < numColors; i++ {
    30  		n.colors = append(n.colors, ids.Empty.Prefix(uint64(i)))
    31  	}
    32  	return n
    33  }
    34  
    35  func (n *Network) AddNode(newConsensusFunc newConsensusFunc) Consensus {
    36  	s := sampler.NewDeterministicUniform(n.rngSource)
    37  	s.Initialize(uint64(len(n.colors)))
    38  	indices, _ := s.Sample(len(n.colors))
    39  
    40  	consensus := newConsensusFunc(n.factory, n.params, n.colors[int(indices[0])])
    41  	for _, index := range indices[1:] {
    42  		consensus.Add(n.colors[int(index)])
    43  	}
    44  
    45  	n.nodes = append(n.nodes, consensus)
    46  	if !consensus.Finalized() {
    47  		n.running = append(n.running, consensus)
    48  	}
    49  
    50  	return consensus
    51  }
    52  
    53  // AddNodeSpecificColor adds a new consensus instance to the network which will
    54  // initially prefer [initialPreference] and additionally adds each of the
    55  // specified [options] to consensus.
    56  func (n *Network) AddNodeSpecificColor(
    57  	newConsensusFunc newConsensusFunc,
    58  	initialPreference int,
    59  	options []int,
    60  ) Consensus {
    61  	consensus := newConsensusFunc(n.factory, n.params, n.colors[initialPreference])
    62  
    63  	for _, i := range options {
    64  		consensus.Add(n.colors[i])
    65  	}
    66  
    67  	n.nodes = append(n.nodes, consensus)
    68  	if !consensus.Finalized() {
    69  		n.running = append(n.running, consensus)
    70  	}
    71  
    72  	return consensus
    73  }
    74  
    75  // Finalized returns true iff every node added to the network has finished
    76  // running.
    77  func (n *Network) Finalized() bool {
    78  	return len(n.running) == 0
    79  }
    80  
    81  // Round simulates a round of consensus by randomly selecting a running node and
    82  // performing an unbiased poll of the nodes in the network for that node.
    83  func (n *Network) Round() {
    84  	if len(n.running) > 0 {
    85  		s := sampler.NewDeterministicUniform(n.rngSource)
    86  
    87  		s.Initialize(uint64(len(n.running)))
    88  		runningInd, _ := s.Next()
    89  		running := n.running[runningInd]
    90  
    91  		s.Initialize(uint64(len(n.nodes)))
    92  		count := min(n.params.K, len(n.nodes))
    93  		indices, _ := s.Sample(count)
    94  		sampledColors := bag.Bag[ids.ID]{}
    95  		for _, index := range indices {
    96  			peer := n.nodes[int(index)]
    97  			sampledColors.Add(peer.Preference())
    98  		}
    99  
   100  		running.RecordPoll(sampledColors)
   101  
   102  		// If this node has been finalized, remove it from the poller
   103  		if running.Finalized() {
   104  			newSize := len(n.running) - 1
   105  			n.running[runningInd] = n.running[newSize]
   106  			n.running = n.running[:newSize]
   107  		}
   108  	}
   109  }
   110  
   111  // Disagreement returns true iff there are any two nodes in the network that
   112  // have finalized two different preferences.
   113  func (n *Network) Disagreement() bool {
   114  	// Iterate [i] to the index of the first node that has finalized.
   115  	i := 0
   116  	for ; i < len(n.nodes) && !n.nodes[i].Finalized(); i++ {
   117  	}
   118  	// If none of the nodes have finalized, then there is no disagreement.
   119  	if i >= len(n.nodes) {
   120  		return false
   121  	}
   122  
   123  	// Return true if any other finalized node has finalized a different
   124  	// preference.
   125  	pref := n.nodes[i].Preference()
   126  	for ; i < len(n.nodes); i++ {
   127  		if node := n.nodes[i]; node.Finalized() && pref != node.Preference() {
   128  			return true
   129  		}
   130  	}
   131  	return false
   132  }
   133  
   134  // Agreement returns true iff every node in the network prefers the same value.
   135  func (n *Network) Agreement() bool {
   136  	if len(n.nodes) == 0 {
   137  		return true
   138  	}
   139  	pref := n.nodes[0].Preference()
   140  	for _, node := range n.nodes {
   141  		if pref != node.Preference() {
   142  			return false
   143  		}
   144  	}
   145  	return true
   146  }