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