github.com/MetalBlockchain/metalgo@v1.11.9/snow/consensus/snowman/topological.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package snowman
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"time"
    11  
    12  	"go.uber.org/zap"
    13  
    14  	"github.com/MetalBlockchain/metalgo/ids"
    15  	"github.com/MetalBlockchain/metalgo/snow"
    16  	"github.com/MetalBlockchain/metalgo/snow/consensus/snowball"
    17  	"github.com/MetalBlockchain/metalgo/utils/bag"
    18  	"github.com/MetalBlockchain/metalgo/utils/set"
    19  )
    20  
    21  var (
    22  	errDuplicateAdd            = errors.New("duplicate block add")
    23  	errUnknownParentBlock      = errors.New("unknown parent block")
    24  	errTooManyProcessingBlocks = errors.New("too many processing blocks")
    25  	errBlockProcessingTooLong  = errors.New("block processing too long")
    26  
    27  	_ Factory   = (*TopologicalFactory)(nil)
    28  	_ Consensus = (*Topological)(nil)
    29  )
    30  
    31  // TopologicalFactory implements Factory by returning a topological struct
    32  type TopologicalFactory struct{}
    33  
    34  func (TopologicalFactory) New() Consensus {
    35  	return &Topological{}
    36  }
    37  
    38  // Topological implements the Snowman interface by using a tree tracking the
    39  // strongly preferred branch. This tree structure amortizes network polls to
    40  // vote on more than just the next block.
    41  type Topological struct {
    42  	metrics *metrics
    43  
    44  	// pollNumber is the number of times RecordPolls has been called
    45  	pollNumber uint64
    46  
    47  	// ctx is the context this snowman instance is executing in
    48  	ctx *snow.ConsensusContext
    49  
    50  	// params are the parameters that should be used to initialize snowball
    51  	// instances
    52  	params snowball.Parameters
    53  
    54  	lastAcceptedID     ids.ID
    55  	lastAcceptedHeight uint64
    56  
    57  	// blocks stores the last accepted block and all the pending blocks
    58  	blocks map[ids.ID]*snowmanBlock // blockID -> snowmanBlock
    59  
    60  	// preferredIDs stores the set of IDs that are currently preferred.
    61  	preferredIDs set.Set[ids.ID]
    62  
    63  	// preferredHeights maps a height to the currently preferred block ID at
    64  	// that height.
    65  	preferredHeights map[uint64]ids.ID // height -> blockID
    66  
    67  	// preference is the preferred block with highest height
    68  	preference ids.ID
    69  
    70  	// Used in [calculateInDegree] and.
    71  	// Should only be accessed in that method.
    72  	// We use this one instance of set.Set instead of creating a
    73  	// new set.Set during each call to [calculateInDegree].
    74  	leaves set.Set[ids.ID]
    75  
    76  	// Kahn nodes used in [calculateInDegree] and [markAncestorInDegrees].
    77  	// Should only be accessed in those methods.
    78  	// We use this one map instead of creating a new map
    79  	// during each call to [calculateInDegree].
    80  	kahnNodes map[ids.ID]kahnNode
    81  }
    82  
    83  // Used to track the kahn topological sort status
    84  type kahnNode struct {
    85  	// inDegree is the number of children that haven't been processed yet. If
    86  	// inDegree is 0, then this node is a leaf
    87  	inDegree int
    88  	// votes for all the children of this node, so far
    89  	votes bag.Bag[ids.ID]
    90  }
    91  
    92  // Used to track which children should receive votes
    93  type votes struct {
    94  	// parentID is the parent of all the votes provided in the votes bag
    95  	parentID ids.ID
    96  	// votes for all the children of the parent
    97  	votes bag.Bag[ids.ID]
    98  }
    99  
   100  func (ts *Topological) Initialize(
   101  	ctx *snow.ConsensusContext,
   102  	params snowball.Parameters,
   103  	lastAcceptedID ids.ID,
   104  	lastAcceptedHeight uint64,
   105  	lastAcceptedTime time.Time,
   106  ) error {
   107  	err := params.Verify()
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	ts.metrics, err = newMetrics(
   113  		ctx.Log,
   114  		ctx.Registerer,
   115  		lastAcceptedHeight,
   116  		lastAcceptedTime,
   117  	)
   118  	if err != nil {
   119  		return err
   120  	}
   121  
   122  	ts.leaves = set.Set[ids.ID]{}
   123  	ts.kahnNodes = make(map[ids.ID]kahnNode)
   124  	ts.ctx = ctx
   125  	ts.params = params
   126  	ts.lastAcceptedID = lastAcceptedID
   127  	ts.lastAcceptedHeight = lastAcceptedHeight
   128  	ts.blocks = map[ids.ID]*snowmanBlock{
   129  		lastAcceptedID: {t: ts},
   130  	}
   131  	ts.preferredHeights = make(map[uint64]ids.ID)
   132  	ts.preference = lastAcceptedID
   133  	return nil
   134  }
   135  
   136  func (ts *Topological) NumProcessing() int {
   137  	return len(ts.blocks) - 1
   138  }
   139  
   140  func (ts *Topological) Add(blk Block) error {
   141  	blkID := blk.ID()
   142  	height := blk.Height()
   143  	ts.ctx.Log.Verbo("adding block",
   144  		zap.Stringer("blkID", blkID),
   145  		zap.Uint64("height", height),
   146  	)
   147  
   148  	// Make sure a block is not inserted twice.
   149  	if ts.Processing(blkID) {
   150  		return errDuplicateAdd
   151  	}
   152  
   153  	ts.metrics.Verified(height)
   154  	ts.metrics.Issued(blkID, ts.pollNumber)
   155  
   156  	parentID := blk.Parent()
   157  	parentNode, ok := ts.blocks[parentID]
   158  	if !ok {
   159  		return errUnknownParentBlock
   160  	}
   161  
   162  	// add the block as a child of its parent, and add the block to the tree
   163  	parentNode.AddChild(blk)
   164  	ts.blocks[blkID] = &snowmanBlock{
   165  		t:   ts,
   166  		blk: blk,
   167  	}
   168  
   169  	// If we are extending the preference, this is the new preference
   170  	if ts.preference == parentID {
   171  		ts.preference = blkID
   172  		ts.preferredIDs.Add(blkID)
   173  		ts.preferredHeights[height] = blkID
   174  	}
   175  
   176  	ts.ctx.Log.Verbo("added block",
   177  		zap.Stringer("blkID", blkID),
   178  		zap.Uint64("height", height),
   179  		zap.Stringer("parentID", parentID),
   180  	)
   181  	return nil
   182  }
   183  
   184  func (ts *Topological) Processing(blkID ids.ID) bool {
   185  	// The last accepted block is in the blocks map, so we first must ensure the
   186  	// requested block isn't the last accepted block.
   187  	if blkID == ts.lastAcceptedID {
   188  		return false
   189  	}
   190  	// If the block is in the map of current blocks and not the last accepted
   191  	// block, then it is currently processing.
   192  	_, ok := ts.blocks[blkID]
   193  	return ok
   194  }
   195  
   196  func (ts *Topological) IsPreferred(blkID ids.ID) bool {
   197  	return blkID == ts.lastAcceptedID || ts.preferredIDs.Contains(blkID)
   198  }
   199  
   200  func (ts *Topological) LastAccepted() (ids.ID, uint64) {
   201  	return ts.lastAcceptedID, ts.lastAcceptedHeight
   202  }
   203  
   204  func (ts *Topological) Preference() ids.ID {
   205  	return ts.preference
   206  }
   207  
   208  func (ts *Topological) PreferenceAtHeight(height uint64) (ids.ID, bool) {
   209  	if height == ts.lastAcceptedHeight {
   210  		return ts.lastAcceptedID, true
   211  	}
   212  	blkID, ok := ts.preferredHeights[height]
   213  	return blkID, ok
   214  }
   215  
   216  // The votes bag contains at most K votes for blocks in the tree. If there is a
   217  // vote for a block that isn't in the tree, the vote is dropped.
   218  //
   219  // Votes are propagated transitively towards the genesis. All blocks in the tree
   220  // that result in at least Alpha votes will record the poll on their children.
   221  // Every other block will have an unsuccessful poll registered.
   222  //
   223  // After collecting which blocks should be voted on, the polls are registered
   224  // and blocks are accepted/rejected as needed. The preference is then updated to
   225  // equal the leaf on the preferred branch.
   226  //
   227  // To optimize the theoretical complexity of the vote propagation, a topological
   228  // sort is done over the blocks that are reachable from the provided votes.
   229  // During the sort, votes are pushed towards the genesis. To prevent interating
   230  // over all blocks that had unsuccessful polls, we set a flag on the block to
   231  // know that any future traversal through that block should register an
   232  // unsuccessful poll on that block and every descendant block.
   233  //
   234  // The complexity of this function is:
   235  // - Runtime = 4 * |live set| + |votes|
   236  // - Space = 2 * |live set| + |votes|
   237  func (ts *Topological) RecordPoll(ctx context.Context, voteBag bag.Bag[ids.ID]) error {
   238  	// Register a new poll call
   239  	ts.pollNumber++
   240  
   241  	var voteStack []votes
   242  	if voteBag.Len() >= ts.params.AlphaPreference {
   243  		// Since we received at least alpha votes, it's possible that
   244  		// we reached an alpha majority on a processing block.
   245  		// We must perform the traversals to calculate all block
   246  		// that reached an alpha majority.
   247  
   248  		// Populates [ts.kahnNodes] and [ts.leaves]
   249  		// Runtime = |live set| + |votes| ; Space = |live set| + |votes|
   250  		ts.calculateInDegree(voteBag)
   251  
   252  		// Runtime = |live set| ; Space = |live set|
   253  		voteStack = ts.pushVotes()
   254  	}
   255  
   256  	// Runtime = |live set| ; Space = Constant
   257  	preferred, err := ts.vote(ctx, voteStack)
   258  	if err != nil {
   259  		return err
   260  	}
   261  
   262  	// If the set of preferred IDs already contains the preference, then the
   263  	// preference is guaranteed to already be set correctly. This is because the
   264  	// value returned from vote reports the next preferred block after the last
   265  	// preferred block that was voted for. If this block was previously
   266  	// preferred, then we know that following the preferences down the chain
   267  	// will return the current preference.
   268  	if ts.preferredIDs.Contains(preferred) {
   269  		return nil
   270  	}
   271  
   272  	// Runtime = 2 * |live set| ; Space = Constant
   273  	ts.preferredIDs.Clear()
   274  	clear(ts.preferredHeights)
   275  
   276  	ts.preference = preferred
   277  	startBlock := ts.blocks[ts.preference]
   278  
   279  	// Runtime = |live set| ; Space = Constant
   280  	// Traverse from the preferred ID to the last accepted ancestor.
   281  	//
   282  	// It is guaranteed that the first decided block we encounter is the last
   283  	// accepted block because the startBlock is the preferred block. The
   284  	// preferred block is guaranteed to either be the last accepted block or
   285  	// extend the accepted chain.
   286  	for block := startBlock; !block.Decided(); {
   287  		blkID := block.blk.ID()
   288  		ts.preferredIDs.Add(blkID)
   289  		ts.preferredHeights[block.blk.Height()] = blkID
   290  		block = ts.blocks[block.blk.Parent()]
   291  	}
   292  	// Traverse from the preferred ID to the preferred child until there are no
   293  	// children.
   294  	for block := startBlock; block.sb != nil; {
   295  		ts.preference = block.sb.Preference()
   296  		ts.preferredIDs.Add(ts.preference)
   297  		block = ts.blocks[ts.preference]
   298  		// Invariant: Because the prior block had an initialized snowball
   299  		// instance, it must have a processing child. This guarantees that
   300  		// block.blk is non-nil here.
   301  		ts.preferredHeights[block.blk.Height()] = ts.preference
   302  	}
   303  	return nil
   304  }
   305  
   306  // HealthCheck returns information about the consensus health.
   307  func (ts *Topological) HealthCheck(context.Context) (interface{}, error) {
   308  	var errs []error
   309  
   310  	numProcessingBlks := ts.NumProcessing()
   311  	if numProcessingBlks > ts.params.MaxOutstandingItems {
   312  		err := fmt.Errorf("%w: %d > %d",
   313  			errTooManyProcessingBlocks,
   314  			numProcessingBlks,
   315  			ts.params.MaxOutstandingItems,
   316  		)
   317  		errs = append(errs, err)
   318  	}
   319  
   320  	maxTimeProcessing := ts.metrics.MeasureAndGetOldestDuration()
   321  	if maxTimeProcessing > ts.params.MaxItemProcessingTime {
   322  		err := fmt.Errorf("%w: %s > %s",
   323  			errBlockProcessingTooLong,
   324  			maxTimeProcessing,
   325  			ts.params.MaxItemProcessingTime,
   326  		)
   327  		errs = append(errs, err)
   328  	}
   329  
   330  	return map[string]interface{}{
   331  		"processingBlocks":       numProcessingBlks,
   332  		"longestProcessingBlock": maxTimeProcessing.String(), // .String() is needed here to ensure a human readable format
   333  		"lastAcceptedID":         ts.lastAcceptedID,
   334  		"lastAcceptedHeight":     ts.lastAcceptedHeight,
   335  	}, errors.Join(errs...)
   336  }
   337  
   338  // takes in a list of votes and sets up the topological ordering. Returns the
   339  // reachable section of the graph annotated with the number of inbound edges and
   340  // the non-transitively applied votes. Also returns the list of leaf blocks.
   341  func (ts *Topological) calculateInDegree(votes bag.Bag[ids.ID]) {
   342  	// Clear the Kahn node set
   343  	clear(ts.kahnNodes)
   344  	// Clear the leaf set
   345  	ts.leaves.Clear()
   346  
   347  	for _, vote := range votes.List() {
   348  		votedBlock, validVote := ts.blocks[vote]
   349  
   350  		// If the vote is for a block that isn't in the current pending set,
   351  		// then the vote is dropped
   352  		if !validVote {
   353  			continue
   354  		}
   355  
   356  		// If the vote is for the last accepted block, the vote is dropped
   357  		if votedBlock.Decided() {
   358  			continue
   359  		}
   360  
   361  		// The parent contains the snowball instance of its children
   362  		parentID := votedBlock.blk.Parent()
   363  
   364  		// Add the votes for this block to the parent's set of responses
   365  		numVotes := votes.Count(vote)
   366  		kahn, previouslySeen := ts.kahnNodes[parentID]
   367  		kahn.votes.AddCount(vote, numVotes)
   368  		ts.kahnNodes[parentID] = kahn
   369  
   370  		// If the parent block already had registered votes, then there is no
   371  		// need to iterate into the parents
   372  		if previouslySeen {
   373  			continue
   374  		}
   375  
   376  		// If I've never seen this parent block before, it is currently a leaf.
   377  		ts.leaves.Add(parentID)
   378  
   379  		// iterate through all the block's ancestors and set up the inDegrees of
   380  		// the blocks
   381  		for n := ts.blocks[parentID]; !n.Decided(); n = ts.blocks[parentID] {
   382  			parentID = n.blk.Parent()
   383  
   384  			// Increase the inDegree by one
   385  			kahn, previouslySeen := ts.kahnNodes[parentID]
   386  			kahn.inDegree++
   387  			ts.kahnNodes[parentID] = kahn
   388  
   389  			// If we have already seen this block, then we shouldn't increase
   390  			// the inDegree of the ancestors through this block again.
   391  			if previouslySeen {
   392  				// Nodes are only leaves if they have no inbound edges.
   393  				ts.leaves.Remove(parentID)
   394  				break
   395  			}
   396  		}
   397  	}
   398  }
   399  
   400  // convert the tree into a branch of snowball instances with at least alpha
   401  // votes
   402  func (ts *Topological) pushVotes() []votes {
   403  	voteStack := make([]votes, 0, len(ts.kahnNodes))
   404  	for ts.leaves.Len() > 0 {
   405  		// Pop one element of [leaves]
   406  		leafID, _ := ts.leaves.Pop()
   407  		// Should never return false because we just
   408  		// checked that [ts.leaves] is non-empty.
   409  
   410  		// get the block and sort information about the block
   411  		kahnNode := ts.kahnNodes[leafID]
   412  		block := ts.blocks[leafID]
   413  
   414  		// If there are at least Alpha votes, then this block needs to record
   415  		// the poll on the snowball instance
   416  		if kahnNode.votes.Len() >= ts.params.AlphaPreference {
   417  			voteStack = append(voteStack, votes{
   418  				parentID: leafID,
   419  				votes:    kahnNode.votes,
   420  			})
   421  		}
   422  
   423  		// If the block is accepted, then we don't need to push votes to the
   424  		// parent block
   425  		if block.Decided() {
   426  			continue
   427  		}
   428  
   429  		parentID := block.blk.Parent()
   430  
   431  		// Remove an inbound edge from the parent kahn node and push the votes.
   432  		parentKahnNode := ts.kahnNodes[parentID]
   433  		parentKahnNode.inDegree--
   434  		parentKahnNode.votes.AddCount(leafID, kahnNode.votes.Len())
   435  		ts.kahnNodes[parentID] = parentKahnNode
   436  
   437  		// If the inDegree is zero, then the parent node is now a leaf
   438  		if parentKahnNode.inDegree == 0 {
   439  			ts.leaves.Add(parentID)
   440  		}
   441  	}
   442  	return voteStack
   443  }
   444  
   445  // apply votes to the branch that received an Alpha threshold and returns the
   446  // next preferred block after the last preferred block that received an Alpha
   447  // threshold.
   448  func (ts *Topological) vote(ctx context.Context, voteStack []votes) (ids.ID, error) {
   449  	// If the voteStack is empty, then the full tree should falter. This won't
   450  	// change the preferred branch.
   451  	if len(voteStack) == 0 {
   452  		lastAcceptedBlock := ts.blocks[ts.lastAcceptedID]
   453  		lastAcceptedBlock.shouldFalter = true
   454  
   455  		if numProcessing := len(ts.blocks) - 1; numProcessing > 0 {
   456  			ts.ctx.Log.Verbo("no progress was made after processing pending blocks",
   457  				zap.Int("numProcessing", numProcessing),
   458  			)
   459  			ts.metrics.FailedPoll()
   460  		}
   461  		return ts.preference, nil
   462  	}
   463  
   464  	// keep track of the new preferred block
   465  	newPreferred := ts.lastAcceptedID
   466  	onPreferredBranch := true
   467  	pollSuccessful := false
   468  	for len(voteStack) > 0 {
   469  		// pop a vote off the stack
   470  		newStackSize := len(voteStack) - 1
   471  		vote := voteStack[newStackSize]
   472  		voteStack = voteStack[:newStackSize]
   473  
   474  		// get the block that we are going to vote on
   475  		parentBlock, notRejected := ts.blocks[vote.parentID]
   476  
   477  		// if the block we are going to vote on was already rejected, then
   478  		// we should stop applying the votes
   479  		if !notRejected {
   480  			break
   481  		}
   482  
   483  		// keep track of transitive falters to propagate to this block's
   484  		// children
   485  		shouldTransitivelyFalter := parentBlock.shouldFalter
   486  
   487  		// if the block was previously marked as needing to falter, the block
   488  		// should falter before applying the vote
   489  		if shouldTransitivelyFalter {
   490  			ts.ctx.Log.Verbo("resetting confidence below parent",
   491  				zap.Stringer("parentID", vote.parentID),
   492  			)
   493  
   494  			parentBlock.sb.RecordUnsuccessfulPoll()
   495  			parentBlock.shouldFalter = false
   496  		}
   497  
   498  		// apply the votes for this snowball instance
   499  		pollSuccessful = parentBlock.sb.RecordPoll(vote.votes) || pollSuccessful
   500  
   501  		// Only accept when you are finalized and a child of the last accepted
   502  		// block.
   503  		if parentBlock.sb.Finalized() && ts.lastAcceptedID == vote.parentID {
   504  			if err := ts.acceptPreferredChild(ctx, parentBlock); err != nil {
   505  				return ids.Empty, err
   506  			}
   507  
   508  			// by accepting the child of parentBlock, the last accepted block is
   509  			// no longer voteParentID, but its child. So, voteParentID can be
   510  			// removed from the tree.
   511  			delete(ts.blocks, vote.parentID)
   512  		}
   513  
   514  		// If we are on the preferred branch, then the parent's preference is
   515  		// the next block on the preferred branch.
   516  		parentPreference := parentBlock.sb.Preference()
   517  		if onPreferredBranch {
   518  			newPreferred = parentPreference
   519  		}
   520  
   521  		// Get the ID of the child that is having a RecordPoll called. All other
   522  		// children will need to have their confidence reset. If there isn't a
   523  		// child having RecordPoll called, then the nextID will default to the
   524  		// nil ID.
   525  		nextID := ids.Empty
   526  		if len(voteStack) > 0 {
   527  			nextID = voteStack[newStackSize-1].parentID
   528  		}
   529  
   530  		// If we are on the preferred branch and the nextID is the preference of
   531  		// the snowball instance, then we are following the preferred branch.
   532  		onPreferredBranch = onPreferredBranch && nextID == parentPreference
   533  
   534  		// If there wasn't an alpha threshold on the branch (either on this vote
   535  		// or a past transitive vote), I should falter now.
   536  		for childID := range parentBlock.children {
   537  			// If we don't need to transitively falter and the child is going to
   538  			// have RecordPoll called on it, then there is no reason to reset
   539  			// the block's confidence
   540  			if !shouldTransitivelyFalter && childID == nextID {
   541  				continue
   542  			}
   543  
   544  			// If we finalized a child of the current block, then all other
   545  			// children will have been rejected and removed from the tree.
   546  			// Therefore, we need to make sure the child is still in the tree.
   547  			childBlock, notRejected := ts.blocks[childID]
   548  			if notRejected {
   549  				ts.ctx.Log.Verbo("defering confidence reset of child block",
   550  					zap.Stringer("childID", childID),
   551  				)
   552  
   553  				ts.ctx.Log.Verbo("voting for next block",
   554  					zap.Stringer("nextID", nextID),
   555  				)
   556  
   557  				// If the child is ever voted for positively, the confidence
   558  				// must be reset first.
   559  				childBlock.shouldFalter = true
   560  			}
   561  		}
   562  	}
   563  
   564  	if pollSuccessful {
   565  		ts.metrics.SuccessfulPoll()
   566  	} else {
   567  		ts.metrics.FailedPoll()
   568  	}
   569  	return newPreferred, nil
   570  }
   571  
   572  // Accepts the preferred child of the provided snowman block. By accepting the
   573  // preferred child, all other children will be rejected. When these children are
   574  // rejected, all their descendants will be rejected.
   575  //
   576  // We accept a block once its parent's snowball instance has finalized
   577  // with it as the preference.
   578  func (ts *Topological) acceptPreferredChild(ctx context.Context, n *snowmanBlock) error {
   579  	// We are finalizing the block's child, so we need to get the preference
   580  	pref := n.sb.Preference()
   581  
   582  	// Get the child and accept it
   583  	child := n.children[pref]
   584  	// Notify anyone listening that this block was accepted.
   585  	bytes := child.Bytes()
   586  	// Note that BlockAcceptor.Accept must be called before child.Accept to
   587  	// honor Acceptor.Accept's invariant.
   588  	if err := ts.ctx.BlockAcceptor.Accept(ts.ctx, pref, bytes); err != nil {
   589  		return err
   590  	}
   591  
   592  	height := child.Height()
   593  	timestamp := child.Timestamp()
   594  	ts.ctx.Log.Trace("accepting block",
   595  		zap.Stringer("blkID", pref),
   596  		zap.Uint64("height", height),
   597  		zap.Time("timestamp", timestamp),
   598  	)
   599  	if err := child.Accept(ctx); err != nil {
   600  		return err
   601  	}
   602  
   603  	// Update the last accepted values to the newly accepted block.
   604  	ts.lastAcceptedID = pref
   605  	ts.lastAcceptedHeight = height
   606  	// Remove the decided block from the set of processing IDs, as its status
   607  	// now implies its preferredness.
   608  	ts.preferredIDs.Remove(pref)
   609  	delete(ts.preferredHeights, height)
   610  
   611  	ts.metrics.Accepted(
   612  		pref,
   613  		height,
   614  		timestamp,
   615  		ts.pollNumber,
   616  		len(bytes),
   617  	)
   618  
   619  	// Because ts.blocks contains the last accepted block, we don't delete the
   620  	// block from the blocks map here.
   621  
   622  	rejects := make([]ids.ID, 0, len(n.children)-1)
   623  	for childID, child := range n.children {
   624  		if childID == pref {
   625  			// don't reject the block we just accepted
   626  			continue
   627  		}
   628  
   629  		ts.ctx.Log.Trace("rejecting block",
   630  			zap.String("reason", "conflict with accepted block"),
   631  			zap.Stringer("blkID", childID),
   632  			zap.Uint64("height", child.Height()),
   633  			zap.Stringer("conflictID", pref),
   634  		)
   635  		if err := child.Reject(ctx); err != nil {
   636  			return err
   637  		}
   638  		ts.metrics.Rejected(childID, ts.pollNumber, len(child.Bytes()))
   639  
   640  		// Track which blocks have been directly rejected
   641  		rejects = append(rejects, childID)
   642  	}
   643  
   644  	// reject all the descendants of the blocks we just rejected
   645  	return ts.rejectTransitively(ctx, rejects)
   646  }
   647  
   648  // Takes in a list of rejected ids and rejects all descendants of these IDs
   649  func (ts *Topological) rejectTransitively(ctx context.Context, rejected []ids.ID) error {
   650  	// the rejected array is treated as a stack, with the next element at index
   651  	// 0 and the last element at the end of the slice.
   652  	for len(rejected) > 0 {
   653  		// pop the rejected ID off the stack
   654  		newRejectedSize := len(rejected) - 1
   655  		rejectedID := rejected[newRejectedSize]
   656  		rejected = rejected[:newRejectedSize]
   657  
   658  		// get the rejected node, and remove it from the tree
   659  		rejectedNode := ts.blocks[rejectedID]
   660  		delete(ts.blocks, rejectedID)
   661  
   662  		for childID, child := range rejectedNode.children {
   663  			ts.ctx.Log.Trace("rejecting block",
   664  				zap.String("reason", "rejected ancestor"),
   665  				zap.Stringer("blkID", childID),
   666  				zap.Uint64("height", child.Height()),
   667  				zap.Stringer("parentID", rejectedID),
   668  			)
   669  			if err := child.Reject(ctx); err != nil {
   670  				return err
   671  			}
   672  			ts.metrics.Rejected(childID, ts.pollNumber, len(child.Bytes()))
   673  
   674  			// add the newly rejected block to the end of the stack
   675  			rejected = append(rejected, childID)
   676  		}
   677  	}
   678  	return nil
   679  }