github.com/kisexp/xdchain@v0.0.0-20211206025815-490d6b732aa7/raft/speculative_chain.go (about)

     1  package raft
     2  
     3  import (
     4  	mapset "github.com/deckarep/golang-set"
     5  	"gopkg.in/oleiade/lane.v1"
     6  
     7  	"github.com/kisexp/xdchain/common"
     8  	"github.com/kisexp/xdchain/core/types"
     9  	"github.com/kisexp/xdchain/log"
    10  )
    11  
    12  // The speculative chain represents blocks that we have minted which haven't been accepted into the chain yet, building
    13  // on each other in a chain. It has three basic operations:
    14  // * add new block to end
    15  // * accept / remove oldest block
    16  // * unwind / remove invalid blocks to the end
    17  //
    18  // Additionally:
    19  // * clear state when we stop minting
    20  // * set the parent when we're not minting (so it's always current)
    21  type speculativeChain struct {
    22  	head                       *types.Block
    23  	unappliedBlocks            *lane.Deque
    24  	expectedInvalidBlockHashes mapset.Set // This is thread-safe. This set is referred to as our "guard" below.
    25  	proposedTxes               mapset.Set // This is thread-safe.
    26  }
    27  
    28  func newSpeculativeChain() *speculativeChain {
    29  	return &speculativeChain{
    30  		head:                       nil,
    31  		unappliedBlocks:            lane.NewDeque(),
    32  		expectedInvalidBlockHashes: mapset.NewSet(),
    33  		proposedTxes:               mapset.NewSet(),
    34  	}
    35  }
    36  
    37  func (chain *speculativeChain) clear(block *types.Block) {
    38  	chain.head = block
    39  	chain.unappliedBlocks = lane.NewDeque()
    40  	chain.expectedInvalidBlockHashes.Clear()
    41  	chain.proposedTxes.Clear()
    42  }
    43  
    44  // Append a new speculative block
    45  func (chain *speculativeChain) extend(block *types.Block) {
    46  	chain.head = block
    47  	chain.recordProposedTransactions(block.Transactions())
    48  	chain.unappliedBlocks.Append(block)
    49  }
    50  
    51  // Set the parent of the speculative chain
    52  //
    53  // Note: This is only called when not minter
    54  func (chain *speculativeChain) setHead(block *types.Block) {
    55  	chain.head = block
    56  }
    57  
    58  // Accept this block, removing it from the speculative chain
    59  func (chain *speculativeChain) accept(acceptedBlock *types.Block) {
    60  	earliestProposedI := chain.unappliedBlocks.Shift()
    61  	var earliestProposed *types.Block
    62  	if nil != earliestProposedI {
    63  		earliestProposed = earliestProposedI.(*types.Block)
    64  	}
    65  
    66  	// There are three possible scenarios:
    67  	// 1. We don't have a record of this block (or any proposed blocks), meaning someone else minted it and we should
    68  	//    add it as the new head of our speculative chain. New blocks from the old leader are still coming in.
    69  	// 2. This block was the first outstanding one we proposed.
    70  	// 3. This block is different from the block we proposed, (also) meaning new blocks are still coming in from the old
    71  	//    leader, but unlike the first scenario, we need to clear all of the speculative chain state because the
    72  	//    `acceptedBlock` takes precedence over our speculative state.
    73  	if earliestProposed == nil {
    74  		chain.head = acceptedBlock
    75  	} else if expectedBlock := earliestProposed.Hash() == acceptedBlock.Hash(); expectedBlock {
    76  		// Remove the txes in this accepted block from our blacklist.
    77  		chain.removeProposedTxes(acceptedBlock)
    78  	} else {
    79  		log.Info("Another node minted; Clearing speculative state", "block", acceptedBlock.Hash())
    80  
    81  		chain.clear(acceptedBlock)
    82  	}
    83  }
    84  
    85  // Remove all blocks in the chain from the specified one until the end
    86  func (chain *speculativeChain) unwindFrom(invalidHash common.Hash, headBlock *types.Block) {
    87  
    88  	// check our "guard" to see if this is a (descendant) block we're
    89  	// expected to be ruled invalid. if we find it, remove from the guard
    90  	if chain.expectedInvalidBlockHashes.Contains(invalidHash) {
    91  		log.Info("Removing expected-invalid block from guard.", "block", invalidHash)
    92  
    93  		chain.expectedInvalidBlockHashes.Remove(invalidHash)
    94  
    95  		return
    96  	}
    97  
    98  	// pop from the RHS repeatedly, updating minter.parent each time. if not
    99  	// our block, add to guard. in all cases, call removeProposedTxes
   100  	for {
   101  		currBlockI := chain.unappliedBlocks.Pop()
   102  
   103  		if nil == currBlockI {
   104  			log.Info("(Popped all blocks from queue.)")
   105  
   106  			break
   107  		}
   108  
   109  		currBlock := currBlockI.(*types.Block)
   110  
   111  		log.Info("Popped block from queue RHS.", "block", currBlock.Hash())
   112  
   113  		// Maintain invariant: the parent always points the last speculative block or the head of the blockchain
   114  		// if there are not speculative blocks.
   115  		if speculativeParentI := chain.unappliedBlocks.Last(); nil != speculativeParentI {
   116  			chain.head = speculativeParentI.(*types.Block)
   117  		} else {
   118  			chain.head = headBlock
   119  		}
   120  
   121  		chain.removeProposedTxes(currBlock)
   122  
   123  		if currBlock.Hash() != invalidHash {
   124  			log.Info("Haven't yet found block; adding descendent to guard.\n", "invalid block", invalidHash, "descendant", currBlock.Hash())
   125  
   126  			chain.expectedInvalidBlockHashes.Add(currBlock.Hash())
   127  		} else {
   128  			break
   129  		}
   130  	}
   131  }
   132  
   133  // We keep track of txes we've put in all newly-mined blocks since the last
   134  // ChainHeadEvent, and filter them out so that we don't try to create blocks
   135  // with the same transactions. This is necessary because the TX pool will keep
   136  // supplying us these transactions until they are in the chain (after having
   137  // flown through raft).
   138  func (chain *speculativeChain) recordProposedTransactions(txes types.Transactions) {
   139  	txHashIs := make([]interface{}, len(txes))
   140  	for i, tx := range txes {
   141  		txHashIs[i] = tx.Hash()
   142  	}
   143  	for _, i := range txHashIs {
   144  		chain.proposedTxes.Add(i)
   145  	}
   146  }
   147  
   148  // Removes txes in block from our "blacklist" of "proposed tx" hashes. When we
   149  // create a new block and use txes from the tx pool, we ignore those that we
   150  // have already used ("proposed"), but that haven't yet officially made it into
   151  // the chain yet.
   152  //
   153  // It's important to remove hashes from this blacklist (once we know we don't
   154  // need them in there anymore) so that it doesn't grow endlessly.
   155  func (chain *speculativeChain) removeProposedTxes(block *types.Block) {
   156  	minedTxes := block.Transactions()
   157  	minedTxInterfaces := make([]interface{}, len(minedTxes))
   158  	for i, tx := range minedTxes {
   159  		minedTxInterfaces[i] = tx.Hash()
   160  	}
   161  
   162  	// NOTE: we are using a thread-safe Set here, so it's fine if we access this
   163  	// here and in mintNewBlock concurrently. using a finer-grained set-specific
   164  	// lock here is preferable, because mintNewBlock holds its locks for a
   165  	// nontrivial amount of time.
   166  	for _, i := range minedTxInterfaces {
   167  		chain.proposedTxes.Remove(i)
   168  	}
   169  }
   170  
   171  func (chain *speculativeChain) withoutProposedTxes(addrTxes AddressTxes) AddressTxes {
   172  	newMap := make(AddressTxes)
   173  
   174  	for addr, txes := range addrTxes {
   175  		filteredTxes := make(types.Transactions, 0)
   176  		for _, tx := range txes {
   177  			if !chain.proposedTxes.Contains(tx.Hash()) {
   178  				filteredTxes = append(filteredTxes, tx)
   179  			}
   180  		}
   181  		if len(filteredTxes) > 0 {
   182  			newMap[addr] = filteredTxes
   183  		}
   184  	}
   185  
   186  	return newMap
   187  }