github.com/ethereum-optimism/optimism@v1.7.2/op-node/rollup/sync/start.go (about) 1 // Package sync is responsible for reconciling L1 and L2. 2 // 3 // The Ethereum chain is a DAG of blocks with the root block being the genesis block. At any given 4 // time, the head (or tip) of the chain can change if an offshoot/branch of the chain has a higher 5 // total difficulty. This is known as a re-organization of the canonical chain. Each block points to 6 // a parent block and the node is responsible for deciding which block is the head and thus the 7 // mapping from block number to canonical block. 8 // 9 // The Optimism (L2) chain has similar properties, but also retains references to the Ethereum (L1) 10 // chain. Each L2 block retains a reference to an L1 block (its "L1 origin", i.e. L1 block 11 // associated with the epoch that the L2 block belongs to) and to its parent L2 block. The L2 chain 12 // node must satisfy the following validity rules: 13 // 14 // 1. l2block.number == l2block.l2parent.block.number + 1 15 // 2. l2block.l1Origin.number >= l2block.l2parent.l1Origin.number 16 // 3. l2block.l1Origin is in the canonical chain on L1 17 // 4. l1_rollup_genesis is an ancestor of l2block.l1Origin 18 // 19 // During normal operation, both the L1 and L2 canonical chains can change, due to a re-organisation 20 // or due to an extension (new L1 or L2 block). 21 // 22 // In particular, in the case of L1 extension, the L2 unsafe head will generally remain the same, 23 // but in the case of an L1 re-org, we need to search for the new safe and unsafe L2 block. 24 package sync 25 26 import ( 27 "context" 28 "errors" 29 "fmt" 30 31 "github.com/ethereum/go-ethereum" 32 "github.com/ethereum/go-ethereum/common" 33 "github.com/ethereum/go-ethereum/log" 34 35 "github.com/ethereum-optimism/optimism/op-node/rollup" 36 "github.com/ethereum-optimism/optimism/op-service/eth" 37 ) 38 39 type L1Chain interface { 40 L1BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L1BlockRef, error) 41 L1BlockRefByNumber(ctx context.Context, number uint64) (eth.L1BlockRef, error) 42 L1BlockRefByHash(ctx context.Context, hash common.Hash) (eth.L1BlockRef, error) 43 } 44 45 type L2Chain interface { 46 L2BlockRefByHash(ctx context.Context, l2Hash common.Hash) (eth.L2BlockRef, error) 47 L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error) 48 } 49 50 var ReorgFinalizedErr = errors.New("cannot reorg finalized block") 51 var WrongChainErr = errors.New("wrong chain") 52 var TooDeepReorgErr = errors.New("reorg is too deep") 53 54 const MaxReorgSeqWindows = 5 55 56 type FindHeadsResult struct { 57 Unsafe eth.L2BlockRef 58 Safe eth.L2BlockRef 59 Finalized eth.L2BlockRef 60 } 61 62 // currentHeads returns the current finalized, safe and unsafe heads of the execution engine. 63 // If nothing has been marked finalized yet, the finalized head defaults to the genesis block. 64 // If nothing has been marked safe yet, the safe head defaults to the finalized block. 65 func currentHeads(ctx context.Context, cfg *rollup.Config, l2 L2Chain) (*FindHeadsResult, error) { 66 finalized, err := l2.L2BlockRefByLabel(ctx, eth.Finalized) 67 if errors.Is(err, ethereum.NotFound) { 68 // default to genesis if we have not finalized anything before. 69 finalized, err = l2.L2BlockRefByHash(ctx, cfg.Genesis.L2.Hash) 70 } 71 if err != nil { 72 return nil, fmt.Errorf("failed to find the finalized L2 block: %w", err) 73 } 74 75 safe, err := l2.L2BlockRefByLabel(ctx, eth.Safe) 76 if errors.Is(err, ethereum.NotFound) { 77 safe = finalized 78 } else if err != nil { 79 return nil, fmt.Errorf("failed to find the safe L2 block: %w", err) 80 } 81 82 unsafe, err := l2.L2BlockRefByLabel(ctx, eth.Unsafe) 83 if err != nil { 84 return nil, fmt.Errorf("failed to find the L2 head block: %w", err) 85 } 86 return &FindHeadsResult{ 87 Unsafe: unsafe, 88 Safe: safe, 89 Finalized: finalized, 90 }, nil 91 } 92 93 // FindL2Heads walks back from `start` (the previous unsafe L2 block) and finds 94 // the finalized, unsafe and safe L2 blocks. 95 // 96 // - The *unsafe L2 block*: This is the highest L2 block whose L1 origin is a *plausible* 97 // extension of the canonical L1 chain (as known to the op-node). 98 // - The *safe L2 block*: This is the highest L2 block whose epoch's sequencing window is 99 // complete within the canonical L1 chain (as known to the op-node). 100 // - The *finalized L2 block*: This is the L2 block which is known to be fully derived from 101 // finalized L1 block data. 102 // 103 // Plausible: meaning that the blockhash of the L2 block's L1 origin 104 // (as reported in the L1 Attributes deposit within the L2 block) is not canonical at another height in the L1 chain, 105 // and the same holds for all its ancestors. 106 func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain, lgr log.Logger, syncCfg *Config) (result *FindHeadsResult, err error) { 107 // Fetch current L2 forkchoice state 108 result, err = currentHeads(ctx, cfg, l2) 109 if err != nil { 110 return nil, fmt.Errorf("failed to fetch current L2 forkchoice state: %w", err) 111 } 112 113 lgr.Info("Loaded current L2 heads", "unsafe", result.Unsafe, "safe", result.Safe, "finalized", result.Finalized, 114 "unsafe_origin", result.Unsafe.L1Origin, "safe_origin", result.Safe.L1Origin) 115 116 // Remember original unsafe block to determine reorg depth 117 prevUnsafe := result.Unsafe 118 119 // Current L2 block. 120 n := result.Unsafe 121 122 var highestL2WithCanonicalL1Origin eth.L2BlockRef // the highest L2 block with confirmed canonical L1 origin 123 var l1Block eth.L1BlockRef // the L1 block at the height of the L1 origin of the current L2 block n. 124 var ahead bool // when "n", the L2 block, has a L1 origin that is not visible in our L1 chain source yet 125 126 ready := false // when we found the block after the safe head, and we just need to return the parent block. 127 128 // Each loop iteration we traverse further from the unsafe head towards the finalized head. 129 // Once we pass the previous safe head and we have seen enough canonical L1 origins to fill a sequence window worth of data, 130 // then we return the last L2 block of the epoch before that as safe head. 131 // Each loop iteration we traverse a single L2 block, and we check if the L1 origins are consistent. 132 for { 133 // Fetch L1 information if we never had it, or if we do not have it for the current origin. 134 // Optimization: as soon as we have a previous L1 block, try to traverse L1 by hash instead of by number, to fill the cache. 135 if n.L1Origin.Hash == l1Block.ParentHash { 136 b, err := l1.L1BlockRefByHash(ctx, n.L1Origin.Hash) 137 if err != nil { 138 // Exit, find-sync start should start over, to move to an available L1 chain with block-by-number / not-found case. 139 return nil, fmt.Errorf("failed to retrieve L1 block: %w", err) 140 } 141 lgr.Info("Walking back L1Block by hash", "curr", l1Block, "next", b, "l2block", n) 142 l1Block = b 143 ahead = false 144 } else if l1Block == (eth.L1BlockRef{}) || n.L1Origin.Hash != l1Block.Hash { 145 b, err := l1.L1BlockRefByNumber(ctx, n.L1Origin.Number) 146 // if L2 is ahead of L1 view, then consider it a "plausible" head 147 notFound := errors.Is(err, ethereum.NotFound) 148 if err != nil && !notFound { 149 return nil, fmt.Errorf("failed to retrieve block %d from L1 for comparison against %s: %w", n.L1Origin.Number, n.L1Origin.Hash, err) 150 } 151 l1Block = b 152 ahead = notFound 153 lgr.Info("Walking back L1Block by number", "curr", l1Block, "next", b, "l2block", n) 154 } 155 156 lgr.Trace("walking sync start", "l2block", n) 157 158 // Don't walk past genesis. If we were at the L2 genesis, but could not find its L1 origin, 159 // the L2 chain is building on the wrong L1 branch. 160 if n.Number == cfg.Genesis.L2.Number { 161 // Check L2 traversal against L2 Genesis data, to make sure the engine is on the correct chain, instead of attempting sync with different L2 destination. 162 if n.Hash != cfg.Genesis.L2.Hash { 163 return nil, fmt.Errorf("%w L2: genesis: %s, got %s", WrongChainErr, cfg.Genesis.L2, n) 164 } 165 // Check L1 comparison against L1 Genesis data, to make sure the L1 data is from the correct chain, instead of attempting sync with different L1 source. 166 if !ahead && l1Block.Hash != cfg.Genesis.L1.Hash { 167 return nil, fmt.Errorf("%w L1: genesis: %s, got %s", WrongChainErr, cfg.Genesis.L1, l1Block) 168 } 169 } 170 // Check L2 traversal against finalized data 171 if (n.Number == result.Finalized.Number) && (n.Hash != result.Finalized.Hash) { 172 return nil, fmt.Errorf("%w: finalized %s, got: %s", ReorgFinalizedErr, result.Finalized, n) 173 } 174 175 // If we don't have a usable unsafe head, then set it 176 if result.Unsafe == (eth.L2BlockRef{}) { 177 result.Unsafe = n 178 // Check we are not reorging L2 incredibly deep 179 if n.L1Origin.Number+(MaxReorgSeqWindows*cfg.SyncLookback()) < prevUnsafe.L1Origin.Number { 180 // If the reorg depth is too large, something is fishy. 181 // This can legitimately happen if L1 goes down for a while. But in that case, 182 // restarting the L2 node with a bigger configured MaxReorgDepth is an acceptable 183 // stopgap solution. 184 return nil, fmt.Errorf("%w: traversed back to L2 block %s, but too deep compared to previous unsafe block %s", TooDeepReorgErr, n, prevUnsafe) 185 } 186 } 187 188 if ahead { 189 // keep the unsafe head if we can't tell if its L1 origin is canonical or not yet. 190 } else if l1Block.Hash == n.L1Origin.Hash { 191 // if L2 matches canonical chain, even if unsafe, 192 // then we can start finding a span of L1 blocks to cover the sequence window, 193 // which may help avoid rewinding the existing safe head unnecessarily. 194 if highestL2WithCanonicalL1Origin == (eth.L2BlockRef{}) { 195 highestL2WithCanonicalL1Origin = n 196 } 197 } else { 198 // L1 origin neither ahead of L1 head nor canonical, discard previous candidate and keep looking. 199 result.Unsafe = eth.L2BlockRef{} 200 highestL2WithCanonicalL1Origin = eth.L2BlockRef{} 201 } 202 203 // If the L2 block is at least as old as the previous safe head, and we have seen at least a full sequence window worth of L1 blocks to confirm 204 if n.Number <= result.Safe.Number && n.L1Origin.Number+cfg.SyncLookback() < highestL2WithCanonicalL1Origin.L1Origin.Number && n.SequenceNumber == 0 { 205 ready = true 206 } 207 208 // Don't traverse further than the finalized head to find a safe head 209 if n.Number == result.Finalized.Number { 210 lgr.Info("Hit finalized L2 head, returning immediately", "unsafe", result.Unsafe, "safe", result.Safe, 211 "finalized", result.Finalized, "unsafe_origin", result.Unsafe.L1Origin, "safe_origin", result.Safe.L1Origin) 212 result.Safe = n 213 return result, nil 214 } 215 216 if syncCfg.SkipSyncStartCheck && highestL2WithCanonicalL1Origin.Hash == n.Hash { 217 lgr.Info("Found highest L2 block with canonical L1 origin. Skip further sanity check and jump to the safe head") 218 n = result.Safe 219 continue 220 } 221 // Pull L2 parent for next iteration 222 parent, err := l2.L2BlockRefByHash(ctx, n.ParentHash) 223 if err != nil { 224 return nil, fmt.Errorf("failed to fetch L2 block by hash %v: %w", n.ParentHash, err) 225 } 226 227 // Check the L1 origin relation 228 if parent.L1Origin != n.L1Origin { 229 // sanity check that the L1 origin block number is coherent 230 if parent.L1Origin.Number+1 != n.L1Origin.Number { 231 return nil, fmt.Errorf("l2 parent %s of %s has L1 origin %s that is not before %s", parent, n, parent.L1Origin, n.L1Origin) 232 } 233 // sanity check that the later sequence number is 0, if it changed between the L2 blocks 234 if n.SequenceNumber != 0 { 235 return nil, fmt.Errorf("l2 block %s has parent %s with different L1 origin %s, but non-zero sequence number %d", n, parent, parent.L1Origin, n.SequenceNumber) 236 } 237 // if the L1 origin is known to be canonical, then the parent must be too 238 if l1Block.Hash == n.L1Origin.Hash && l1Block.ParentHash != parent.L1Origin.Hash { 239 return nil, fmt.Errorf("parent L2 block %s has origin %s but expected %s", parent, parent.L1Origin, l1Block.ParentHash) 240 } 241 } else { 242 if parent.SequenceNumber+1 != n.SequenceNumber { 243 return nil, fmt.Errorf("sequence number inconsistency %d <> %d between l2 blocks %s and %s", parent.SequenceNumber, n.SequenceNumber, parent, n) 244 } 245 } 246 247 n = parent 248 249 // once we found the block at seq nr 0 that is more than a full seq window behind the common chain post-reorg, then use the parent block as safe head. 250 if ready { 251 result.Safe = n 252 return result, nil 253 } 254 } 255 }