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 }