github.com/onflow/flow-go@v0.33.17/engine/execution/ingestion/loader/unexecuted_loader.go (about) 1 package loader 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/rs/zerolog" 8 9 "github.com/onflow/flow-go/engine/execution/state" 10 "github.com/onflow/flow-go/model/flow" 11 "github.com/onflow/flow-go/state/protocol" 12 "github.com/onflow/flow-go/storage" 13 "github.com/onflow/flow-go/utils/logging" 14 ) 15 16 // deprecated. Storehouse is going to use unfinalized loader instead 17 type UnexecutedLoader struct { 18 log zerolog.Logger 19 state protocol.State 20 headers storage.Headers 21 execState state.ExecutionState 22 } 23 24 func NewUnexecutedLoader( 25 log zerolog.Logger, 26 state protocol.State, 27 headers storage.Headers, 28 execState state.ExecutionState, 29 ) *UnexecutedLoader { 30 return &UnexecutedLoader{ 31 log: log.With().Str("component", "ingestion_engine_unexecuted_loader").Logger(), 32 state: state, 33 headers: headers, 34 execState: execState, 35 } 36 } 37 38 // LoadUnexecuted loads all unexecuted and validated blocks 39 // any error returned are exceptions 40 func (e *UnexecutedLoader) LoadUnexecuted(ctx context.Context) ([]flow.Identifier, error) { 41 // saving an executed block is currently not transactional, so it's possible 42 // the block is marked as executed but the receipt might not be saved during a crash. 43 // in order to mitigate this problem, we always re-execute the last executed and finalized 44 // block. 45 // there is an exception, if the last executed block is a root block, then don't execute it, 46 // because the root has already been executed during bootstrapping phase. And re-executing 47 // a root block will fail, because the root block doesn't have a parent block, and could not 48 // get the result of it. 49 // TODO: remove this, when saving a executed block is transactional 50 lastExecutedHeight, lastExecutedID, err := e.execState.GetHighestExecutedBlockID(ctx) 51 if err != nil { 52 return nil, fmt.Errorf("could not get last executed: %w", err) 53 } 54 55 last, err := e.headers.ByBlockID(lastExecutedID) 56 if err != nil { 57 return nil, fmt.Errorf("could not get last executed final by ID: %w", err) 58 } 59 60 // don't reload root block 61 rootBlock, err := e.state.Params().SealedRoot() 62 if err != nil { 63 return nil, fmt.Errorf("failed to retrieve root block: %w", err) 64 } 65 66 blockIDs := make([]flow.Identifier, 0) 67 isRoot := rootBlock.ID() == last.ID() 68 if !isRoot { 69 executed, err := e.execState.IsBlockExecuted(lastExecutedHeight, lastExecutedID) 70 if err != nil { 71 return nil, fmt.Errorf("cannot check is last exeucted final block has been executed %v: %w", lastExecutedID, err) 72 } 73 if !executed { 74 // this should not happen, but if it does, execution should still work 75 e.log.Warn(). 76 Hex("block_id", lastExecutedID[:]). 77 Msg("block marked as highest executed one, but not executable - internal inconsistency") 78 79 blockIDs = append(blockIDs, lastExecutedID) 80 } 81 } 82 83 finalized, pending, err := e.unexecutedBlocks(ctx) 84 if err != nil { 85 return nil, fmt.Errorf("could not reload unexecuted blocks: %w", err) 86 } 87 88 unexecuted := append(finalized, pending...) 89 90 log := e.log.With(). 91 Int("total", len(unexecuted)). 92 Int("finalized", len(finalized)). 93 Int("pending", len(pending)). 94 Uint64("last_executed", lastExecutedHeight). 95 Hex("last_executed_id", lastExecutedID[:]). 96 Logger() 97 98 log.Info().Msg("reloading unexecuted blocks") 99 100 for _, blockID := range unexecuted { 101 blockIDs = append(blockIDs, blockID) 102 e.log.Debug().Hex("block_id", blockID[:]).Msg("reloaded block") 103 } 104 105 log.Info().Msg("all unexecuted have been successfully reloaded") 106 107 return blockIDs, nil 108 } 109 110 func (e *UnexecutedLoader) unexecutedBlocks(ctx context.Context) ( 111 finalized []flow.Identifier, 112 pending []flow.Identifier, 113 err error, 114 ) { 115 // pin the snapshot so that finalizedUnexecutedBlocks and pendingUnexecutedBlocks are based 116 // on the same snapshot. 117 snapshot := e.state.Final() 118 119 finalized, err = e.finalizedUnexecutedBlocks(ctx, snapshot) 120 if err != nil { 121 return nil, nil, fmt.Errorf("could not read finalized unexecuted blocks") 122 } 123 124 pending, err = e.pendingUnexecutedBlocks(ctx, snapshot) 125 if err != nil { 126 return nil, nil, fmt.Errorf("could not read pending unexecuted blocks") 127 } 128 129 return finalized, pending, nil 130 } 131 132 func (e *UnexecutedLoader) finalizedUnexecutedBlocks(ctx context.Context, finalized protocol.Snapshot) ( 133 []flow.Identifier, 134 error, 135 ) { 136 // get finalized height 137 final, err := finalized.Head() 138 if err != nil { 139 return nil, fmt.Errorf("could not get finalized block: %w", err) 140 } 141 142 // find the first unexecuted and finalized block 143 // We iterate from the last finalized, check if it has been executed, 144 // if not, keep going to the lower height, until we find an executed 145 // block, and then the next height is the first unexecuted. 146 // If there is only one finalized, and it's executed (i.e. root block), 147 // then the firstUnexecuted is a unfinalized block, which is ok, 148 // because the next loop will ensure it only iterates through finalized 149 // blocks. 150 lastExecuted := final.Height 151 152 // dynamically bootstrapped execution node will reload blocks from 153 // [sealedRoot.Height + 1, finalizedRoot.Height] and execute them on startup. 154 rootBlock, err := e.state.Params().SealedRoot() 155 if err != nil { 156 return nil, fmt.Errorf("failed to retrieve root block: %w", err) 157 } 158 159 for ; lastExecuted > rootBlock.Height; lastExecuted-- { 160 finalizedID, err := e.headers.BlockIDByHeight(lastExecuted) 161 if err != nil { 162 return nil, fmt.Errorf("could not get header at height: %v, %w", lastExecuted, err) 163 } 164 165 executed, err := e.execState.IsBlockExecuted(lastExecuted, finalizedID) 166 if err != nil { 167 return nil, fmt.Errorf("could not check whether block is executed: %w", err) 168 } 169 170 if executed { 171 break 172 } 173 } 174 175 firstUnexecuted := lastExecuted + 1 176 177 unexecuted := make([]flow.Identifier, 0) 178 179 // starting from the first unexecuted block, go through each unexecuted and finalized block 180 // reload its block to execution queues 181 for height := firstUnexecuted; height <= final.Height; height++ { 182 finalizedID, err := e.headers.BlockIDByHeight(height) 183 if err != nil { 184 return nil, fmt.Errorf("could not get header at height: %v, %w", height, err) 185 } 186 187 unexecuted = append(unexecuted, finalizedID) 188 } 189 190 e.log.Info(). 191 Uint64("last_finalized", final.Height). 192 Uint64("last_finalized_executed", lastExecuted). 193 Uint64("sealed_root_height", rootBlock.Height). 194 Hex("sealed_root_id", logging.Entity(rootBlock)). 195 Uint64("first_unexecuted", firstUnexecuted). 196 Int("total_finalized_unexecuted", len(unexecuted)). 197 Msgf("finalized unexecuted blocks") 198 199 return unexecuted, nil 200 } 201 202 func (e *UnexecutedLoader) pendingUnexecutedBlocks(ctx context.Context, finalized protocol.Snapshot) ( 203 []flow.Identifier, 204 error, 205 ) { 206 pendings, err := finalized.Descendants() 207 if err != nil { 208 return nil, fmt.Errorf("could not get pending blocks: %w", err) 209 } 210 211 unexecuted := make([]flow.Identifier, 0) 212 213 for _, pending := range pendings { 214 p, err := e.headers.ByBlockID(pending) 215 if err != nil { 216 return nil, fmt.Errorf("could not get header by block id: %w", err) 217 } 218 executed, err := e.execState.IsBlockExecuted(p.Height, pending) 219 if err != nil { 220 return nil, fmt.Errorf("could not check block executed or not: %w", err) 221 } 222 223 if !executed { 224 unexecuted = append(unexecuted, pending) 225 } 226 } 227 228 return unexecuted, nil 229 }