github.com/ethereum-optimism/optimism@v1.7.2/op-node/rollup/types_test.go (about) 1 package rollup 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "math/big" 8 "math/rand" 9 "testing" 10 "time" 11 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 15 "github.com/ethereum/go-ethereum/common" 16 17 "github.com/ethereum-optimism/optimism/op-service/eth" 18 ) 19 20 func randConfig() *Config { 21 rng := rand.New(rand.NewSource(1234)) 22 randHash := func() (out [32]byte) { 23 rng.Read(out[:]) 24 return 25 } 26 randAddr := func() (out common.Address) { // we need generics... 27 rng.Read(out[:]) 28 return 29 } 30 return &Config{ 31 Genesis: Genesis{ 32 L1: eth.BlockID{Hash: randHash(), Number: 424242}, 33 L2: eth.BlockID{Hash: randHash(), Number: 1337}, 34 L2Time: uint64(time.Now().Unix()), 35 SystemConfig: eth.SystemConfig{ 36 BatcherAddr: randAddr(), 37 Overhead: randHash(), 38 Scalar: randHash(), 39 GasLimit: 1234567, 40 }, 41 }, 42 BlockTime: 2, 43 MaxSequencerDrift: 100, 44 SeqWindowSize: 2, 45 ChannelTimeout: 123, 46 L1ChainID: big.NewInt(900), 47 L2ChainID: big.NewInt(901), 48 BatchInboxAddress: randAddr(), 49 DepositContractAddress: randAddr(), 50 L1SystemConfigAddress: randAddr(), 51 } 52 } 53 54 func TestConfigJSON(t *testing.T) { 55 config := randConfig() 56 data, err := json.Marshal(config) 57 assert.NoError(t, err) 58 var roundTripped Config 59 assert.NoError(t, json.Unmarshal(data, &roundTripped)) 60 assert.Equal(t, &roundTripped, config) 61 } 62 63 type mockL1Client struct { 64 chainID *big.Int 65 Hash common.Hash 66 } 67 68 func (m *mockL1Client) ChainID(context.Context) (*big.Int, error) { 69 return m.chainID, nil 70 } 71 72 func (m *mockL1Client) L1BlockRefByNumber(ctx context.Context, number uint64) (eth.L1BlockRef, error) { 73 return eth.L1BlockRef{ 74 Hash: m.Hash, 75 Number: 100, 76 }, nil 77 } 78 79 func TestValidateL1Config(t *testing.T) { 80 config := randConfig() 81 config.L1ChainID = big.NewInt(100) 82 config.Genesis.L1.Number = 100 83 config.Genesis.L1.Hash = [32]byte{0x01} 84 mockClient := mockL1Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}} 85 err := config.ValidateL1Config(context.TODO(), &mockClient) 86 assert.NoError(t, err) 87 } 88 89 func TestValidateL1ConfigInvalidChainIdFails(t *testing.T) { 90 config := randConfig() 91 config.L1ChainID = big.NewInt(101) 92 config.Genesis.L1.Number = 100 93 config.Genesis.L1.Hash = [32]byte{0x01} 94 mockClient := mockL1Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}} 95 err := config.ValidateL1Config(context.TODO(), &mockClient) 96 assert.Error(t, err) 97 config.L1ChainID = big.NewInt(99) 98 err = config.ValidateL1Config(context.TODO(), &mockClient) 99 assert.Error(t, err) 100 } 101 102 func TestValidateL1ConfigInvalidGenesisHashFails(t *testing.T) { 103 config := randConfig() 104 config.L1ChainID = big.NewInt(100) 105 config.Genesis.L1.Number = 100 106 config.Genesis.L1.Hash = [32]byte{0x00} 107 mockClient := mockL1Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}} 108 err := config.ValidateL1Config(context.TODO(), &mockClient) 109 assert.Error(t, err) 110 config.Genesis.L1.Hash = [32]byte{0x02} 111 err = config.ValidateL1Config(context.TODO(), &mockClient) 112 assert.Error(t, err) 113 } 114 115 func TestCheckL1ChainID(t *testing.T) { 116 config := randConfig() 117 config.L1ChainID = big.NewInt(100) 118 err := config.CheckL1ChainID(context.TODO(), &mockL1Client{chainID: big.NewInt(100)}) 119 assert.NoError(t, err) 120 err = config.CheckL1ChainID(context.TODO(), &mockL1Client{chainID: big.NewInt(101)}) 121 assert.Error(t, err) 122 err = config.CheckL1ChainID(context.TODO(), &mockL1Client{chainID: big.NewInt(99)}) 123 assert.Error(t, err) 124 } 125 126 func TestCheckL1BlockRefByNumber(t *testing.T) { 127 config := randConfig() 128 config.Genesis.L1.Number = 100 129 config.Genesis.L1.Hash = [32]byte{0x01} 130 mockClient := mockL1Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}} 131 err := config.CheckL1GenesisBlockHash(context.TODO(), &mockClient) 132 assert.NoError(t, err) 133 mockClient.Hash = common.Hash{0x02} 134 err = config.CheckL1GenesisBlockHash(context.TODO(), &mockClient) 135 assert.Error(t, err) 136 mockClient.Hash = common.Hash{0x00} 137 err = config.CheckL1GenesisBlockHash(context.TODO(), &mockClient) 138 assert.Error(t, err) 139 } 140 141 // TestRandomConfigDescription tests that the description works for different variations of a random rollup config. 142 func TestRandomConfigDescription(t *testing.T) { 143 t.Run("named L2", func(t *testing.T) { 144 config := randConfig() 145 out := config.Description(map[string]string{config.L2ChainID.String(): "foobar chain"}) 146 require.Contains(t, out, "foobar chain") 147 }) 148 t.Run("named L1", func(t *testing.T) { 149 config := randConfig() 150 config.L1ChainID = big.NewInt(5) 151 out := config.Description(map[string]string{config.L2ChainID.String(): "foobar chain"}) 152 require.Contains(t, out, "goerli") 153 }) 154 t.Run("unnamed", func(t *testing.T) { 155 config := randConfig() 156 out := config.Description(nil) 157 require.Contains(t, out, "(unknown L1)") 158 require.Contains(t, out, "(unknown L2)") 159 }) 160 t.Run("regolith unset", func(t *testing.T) { 161 config := randConfig() 162 config.RegolithTime = nil 163 out := config.Description(nil) 164 require.Contains(t, out, "Regolith: (not configured)") 165 }) 166 t.Run("regolith genesis", func(t *testing.T) { 167 config := randConfig() 168 config.RegolithTime = new(uint64) 169 out := config.Description(nil) 170 require.Contains(t, out, "Regolith: @ genesis") 171 }) 172 t.Run("regolith date", func(t *testing.T) { 173 config := randConfig() 174 x := uint64(1677119335) 175 config.RegolithTime = &x 176 out := config.Description(nil) 177 // Don't check human-readable part of the date, it's timezone-dependent. 178 // Don't make this test fail only in Australia :') 179 require.Contains(t, out, fmt.Sprintf("Regolith: @ %d ~ ", x)) 180 }) 181 } 182 183 // TestRegolithActivation tests the activation condition of the Regolith upgrade. 184 func TestRegolithActivation(t *testing.T) { 185 config := randConfig() 186 config.RegolithTime = nil 187 require.False(t, config.IsRegolith(0), "false if nil time, even if checking 0") 188 require.False(t, config.IsRegolith(123456), "false if nil time") 189 config.RegolithTime = new(uint64) 190 require.True(t, config.IsRegolith(0), "true at zero") 191 require.True(t, config.IsRegolith(123456), "true for any") 192 x := uint64(123) 193 config.RegolithTime = &x 194 require.False(t, config.IsRegolith(0)) 195 require.False(t, config.IsRegolith(122)) 196 require.True(t, config.IsRegolith(123)) 197 require.True(t, config.IsRegolith(124)) 198 } 199 200 type mockL2Client struct { 201 chainID *big.Int 202 Hash common.Hash 203 } 204 205 func (m *mockL2Client) ChainID(context.Context) (*big.Int, error) { 206 return m.chainID, nil 207 } 208 209 func (m *mockL2Client) L2BlockRefByNumber(ctx context.Context, number uint64) (eth.L2BlockRef, error) { 210 return eth.L2BlockRef{ 211 Hash: m.Hash, 212 Number: 100, 213 }, nil 214 } 215 216 func TestValidateL2Config(t *testing.T) { 217 config := randConfig() 218 config.L2ChainID = big.NewInt(100) 219 config.Genesis.L2.Number = 100 220 config.Genesis.L2.Hash = [32]byte{0x01} 221 mockClient := mockL2Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}} 222 err := config.ValidateL2Config(context.TODO(), &mockClient, false) 223 assert.NoError(t, err) 224 } 225 226 func TestValidateL2ConfigInvalidChainIdFails(t *testing.T) { 227 config := randConfig() 228 config.L2ChainID = big.NewInt(101) 229 config.Genesis.L2.Number = 100 230 config.Genesis.L2.Hash = [32]byte{0x01} 231 mockClient := mockL2Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}} 232 err := config.ValidateL2Config(context.TODO(), &mockClient, false) 233 assert.Error(t, err) 234 config.L2ChainID = big.NewInt(99) 235 err = config.ValidateL2Config(context.TODO(), &mockClient, false) 236 assert.Error(t, err) 237 } 238 239 func TestValidateL2ConfigInvalidGenesisHashFails(t *testing.T) { 240 config := randConfig() 241 config.L2ChainID = big.NewInt(100) 242 config.Genesis.L2.Number = 100 243 config.Genesis.L2.Hash = [32]byte{0x00} 244 mockClient := mockL2Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}} 245 err := config.ValidateL2Config(context.TODO(), &mockClient, false) 246 assert.Error(t, err) 247 config.Genesis.L2.Hash = [32]byte{0x02} 248 err = config.ValidateL2Config(context.TODO(), &mockClient, false) 249 assert.Error(t, err) 250 } 251 252 func TestValidateL2ConfigInvalidGenesisHashSkippedWhenRequested(t *testing.T) { 253 config := randConfig() 254 config.L2ChainID = big.NewInt(100) 255 config.Genesis.L2.Number = 100 256 config.Genesis.L2.Hash = [32]byte{0x00} 257 mockClient := mockL2Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}} 258 err := config.ValidateL2Config(context.TODO(), &mockClient, true) 259 assert.NoError(t, err) 260 config.Genesis.L2.Hash = [32]byte{0x02} 261 err = config.ValidateL2Config(context.TODO(), &mockClient, true) 262 assert.NoError(t, err) 263 } 264 265 func TestCheckL2ChainID(t *testing.T) { 266 config := randConfig() 267 config.L2ChainID = big.NewInt(100) 268 err := config.CheckL2ChainID(context.TODO(), &mockL2Client{chainID: big.NewInt(100)}) 269 assert.NoError(t, err) 270 err = config.CheckL2ChainID(context.TODO(), &mockL2Client{chainID: big.NewInt(101)}) 271 assert.Error(t, err) 272 err = config.CheckL2ChainID(context.TODO(), &mockL2Client{chainID: big.NewInt(99)}) 273 assert.Error(t, err) 274 } 275 276 func TestCheckL2BlockRefByNumber(t *testing.T) { 277 config := randConfig() 278 config.Genesis.L2.Number = 100 279 config.Genesis.L2.Hash = [32]byte{0x01} 280 mockClient := mockL2Client{chainID: big.NewInt(100), Hash: common.Hash{0x01}} 281 err := config.CheckL2GenesisBlockHash(context.TODO(), &mockClient) 282 assert.NoError(t, err) 283 mockClient.Hash = common.Hash{0x02} 284 err = config.CheckL2GenesisBlockHash(context.TODO(), &mockClient) 285 assert.Error(t, err) 286 mockClient.Hash = common.Hash{0x00} 287 err = config.CheckL2GenesisBlockHash(context.TODO(), &mockClient) 288 assert.Error(t, err) 289 } 290 291 func TestConfig_Check(t *testing.T) { 292 tests := []struct { 293 name string 294 modifier func(cfg *Config) 295 expectedErr error 296 }{ 297 { 298 name: "BlockTimeZero", 299 modifier: func(cfg *Config) { cfg.BlockTime = 0 }, 300 expectedErr: ErrBlockTimeZero, 301 }, 302 { 303 name: "ChannelTimeoutZero", 304 modifier: func(cfg *Config) { cfg.ChannelTimeout = 0 }, 305 expectedErr: ErrMissingChannelTimeout, 306 }, 307 { 308 name: "SeqWindowSizeZero", 309 modifier: func(cfg *Config) { cfg.SeqWindowSize = 0 }, 310 expectedErr: ErrInvalidSeqWindowSize, 311 }, 312 { 313 name: "SeqWindowSizeOne", 314 modifier: func(cfg *Config) { cfg.SeqWindowSize = 1 }, 315 expectedErr: ErrInvalidSeqWindowSize, 316 }, 317 { 318 name: "NoL1Genesis", 319 modifier: func(cfg *Config) { cfg.Genesis.L1.Hash = common.Hash{} }, 320 expectedErr: ErrMissingGenesisL1Hash, 321 }, 322 { 323 name: "NoL2Genesis", 324 modifier: func(cfg *Config) { cfg.Genesis.L2.Hash = common.Hash{} }, 325 expectedErr: ErrMissingGenesisL2Hash, 326 }, 327 { 328 name: "GenesisHashesEqual", 329 modifier: func(cfg *Config) { cfg.Genesis.L2.Hash = cfg.Genesis.L1.Hash }, 330 expectedErr: ErrGenesisHashesSame, 331 }, 332 { 333 name: "GenesisL2TimeZero", 334 modifier: func(cfg *Config) { cfg.Genesis.L2Time = 0 }, 335 expectedErr: ErrMissingGenesisL2Time, 336 }, 337 { 338 name: "NoBatcherAddr", 339 modifier: func(cfg *Config) { cfg.Genesis.SystemConfig.BatcherAddr = common.Address{} }, 340 expectedErr: ErrMissingBatcherAddr, 341 }, 342 { 343 name: "NoOverhead", 344 modifier: func(cfg *Config) { cfg.Genesis.SystemConfig.Overhead = eth.Bytes32{} }, 345 expectedErr: ErrMissingOverhead, 346 }, 347 { 348 name: "NoScalar", 349 modifier: func(cfg *Config) { cfg.Genesis.SystemConfig.Scalar = eth.Bytes32{} }, 350 expectedErr: ErrMissingScalar, 351 }, 352 { 353 name: "NoGasLimit", 354 modifier: func(cfg *Config) { cfg.Genesis.SystemConfig.GasLimit = 0 }, 355 expectedErr: ErrMissingGasLimit, 356 }, 357 { 358 name: "NoBatchInboxAddress", 359 modifier: func(cfg *Config) { cfg.BatchInboxAddress = common.Address{} }, 360 expectedErr: ErrMissingBatchInboxAddress, 361 }, 362 { 363 name: "NoDepositContractAddress", 364 modifier: func(cfg *Config) { cfg.DepositContractAddress = common.Address{} }, 365 expectedErr: ErrMissingDepositContractAddress, 366 }, 367 { 368 name: "NoL1ChainId", 369 modifier: func(cfg *Config) { cfg.L1ChainID = nil }, 370 expectedErr: ErrMissingL1ChainID, 371 }, 372 { 373 name: "NoL2ChainId", 374 modifier: func(cfg *Config) { cfg.L2ChainID = nil }, 375 expectedErr: ErrMissingL2ChainID, 376 }, 377 { 378 name: "ChainIDsEqual", 379 modifier: func(cfg *Config) { cfg.L2ChainID = cfg.L1ChainID }, 380 expectedErr: ErrChainIDsSame, 381 }, 382 { 383 name: "L1ChainIdNegative", 384 modifier: func(cfg *Config) { cfg.L1ChainID = big.NewInt(-1) }, 385 expectedErr: ErrL1ChainIDNotPositive, 386 }, 387 { 388 name: "L1ChainIdZero", 389 modifier: func(cfg *Config) { cfg.L1ChainID = big.NewInt(0) }, 390 expectedErr: ErrL1ChainIDNotPositive, 391 }, 392 { 393 name: "L2ChainIdNegative", 394 modifier: func(cfg *Config) { cfg.L2ChainID = big.NewInt(-1) }, 395 expectedErr: ErrL2ChainIDNotPositive, 396 }, 397 { 398 name: "L2ChainIdZero", 399 modifier: func(cfg *Config) { cfg.L2ChainID = big.NewInt(0) }, 400 expectedErr: ErrL2ChainIDNotPositive, 401 }, 402 } 403 for _, test := range tests { 404 t.Run(test.name, func(t *testing.T) { 405 cfg := randConfig() 406 test.modifier(cfg) 407 err := cfg.Check() 408 assert.Same(t, err, test.expectedErr) 409 }) 410 } 411 412 forkTests := []struct { 413 name string 414 modifier func(cfg *Config) 415 expectedErr error 416 }{ 417 { 418 name: "PriorForkMissing", 419 modifier: func(cfg *Config) { 420 ecotoneTime := uint64(1) 421 cfg.EcotoneTime = &ecotoneTime 422 }, 423 expectedErr: fmt.Errorf("fork ecotone set (to 1), but prior fork delta missing"), 424 }, 425 { 426 name: "PriorForkHasHigherOffset", 427 modifier: func(cfg *Config) { 428 regolithTime := uint64(2) 429 canyonTime := uint64(1) 430 cfg.RegolithTime = ®olithTime 431 cfg.CanyonTime = &canyonTime 432 }, 433 expectedErr: fmt.Errorf("fork canyon set to 1, but prior fork regolith has higher offset 2"), 434 }, 435 { 436 name: "PriorForkOK", 437 modifier: func(cfg *Config) { 438 regolithTime := uint64(1) 439 canyonTime := uint64(2) 440 deltaTime := uint64(3) 441 ecotoneTime := uint64(4) 442 cfg.RegolithTime = ®olithTime 443 cfg.CanyonTime = &canyonTime 444 cfg.DeltaTime = &deltaTime 445 cfg.EcotoneTime = &ecotoneTime 446 }, 447 expectedErr: nil, 448 }, 449 } 450 451 for _, test := range forkTests { 452 t.Run(test.name, func(t *testing.T) { 453 cfg := randConfig() 454 test.modifier(cfg) 455 err := cfg.Check() 456 assert.Equal(t, err, test.expectedErr) 457 }) 458 } 459 } 460 461 func TestTimestampForBlock(t *testing.T) { 462 config := randConfig() 463 464 tests := []struct { 465 name string 466 genesisTime uint64 467 genesisBlock uint64 468 blockTime uint64 469 blockNum uint64 470 expectedBlockTime uint64 471 }{ 472 { 473 name: "FirstBlock", 474 genesisTime: 100, 475 genesisBlock: 0, 476 blockTime: 2, 477 blockNum: 0, 478 expectedBlockTime: 100, 479 }, 480 { 481 name: "SecondBlock", 482 genesisTime: 100, 483 genesisBlock: 0, 484 blockTime: 2, 485 blockNum: 1, 486 expectedBlockTime: 102, 487 }, 488 { 489 name: "NBlock", 490 genesisTime: 100, 491 genesisBlock: 0, 492 blockTime: 2, 493 blockNum: 25, 494 expectedBlockTime: 150, 495 }, 496 } 497 498 for _, test := range tests { 499 test := test 500 t.Run(fmt.Sprintf("TestTimestampForBlock_%s", test.name), func(t *testing.T) { 501 config.Genesis.L2Time = test.genesisTime 502 config.Genesis.L2.Number = test.genesisBlock 503 config.BlockTime = test.blockTime 504 505 timestamp := config.TimestampForBlock(test.blockNum) 506 assert.Equal(t, timestamp, test.expectedBlockTime) 507 }) 508 } 509 510 } 511 512 func TestForkchoiceUpdatedVersion(t *testing.T) { 513 config := randConfig() 514 tests := []struct { 515 name string 516 canyonTime uint64 517 ecotoneTime uint64 518 attrs *eth.PayloadAttributes 519 expectedMethod eth.EngineAPIMethod 520 }{ 521 { 522 name: "NoAttrs", 523 canyonTime: 10, 524 ecotoneTime: 20, 525 attrs: nil, 526 expectedMethod: eth.FCUV3, 527 }, 528 { 529 name: "BeforeCanyon", 530 canyonTime: 10, 531 ecotoneTime: 20, 532 attrs: ð.PayloadAttributes{Timestamp: 5}, 533 expectedMethod: eth.FCUV1, 534 }, 535 { 536 name: "Canyon", 537 canyonTime: 10, 538 ecotoneTime: 20, 539 attrs: ð.PayloadAttributes{Timestamp: 15}, 540 expectedMethod: eth.FCUV2, 541 }, 542 { 543 name: "Ecotone", 544 canyonTime: 10, 545 ecotoneTime: 20, 546 attrs: ð.PayloadAttributes{Timestamp: 25}, 547 expectedMethod: eth.FCUV3, 548 }, 549 } 550 551 for _, test := range tests { 552 test := test 553 t.Run(fmt.Sprintf("TestForkchoiceUpdatedVersion_%s", test.name), func(t *testing.T) { 554 config.CanyonTime = &test.canyonTime 555 config.EcotoneTime = &test.ecotoneTime 556 assert.Equal(t, config.ForkchoiceUpdatedVersion(test.attrs), test.expectedMethod) 557 }) 558 } 559 } 560 561 func TestNewPayloadVersion(t *testing.T) { 562 config := randConfig() 563 canyonTime := uint64(0) 564 config.CanyonTime = &canyonTime 565 tests := []struct { 566 name string 567 ecotoneTime uint64 568 payloadTime uint64 569 expectedMethod eth.EngineAPIMethod 570 }{ 571 { 572 name: "BeforeEcotone", 573 ecotoneTime: 10, 574 payloadTime: 5, 575 expectedMethod: eth.NewPayloadV2, 576 }, 577 { 578 name: "Ecotone", 579 ecotoneTime: 10, 580 payloadTime: 15, 581 expectedMethod: eth.NewPayloadV3, 582 }, 583 } 584 585 for _, test := range tests { 586 test := test 587 t.Run(fmt.Sprintf("TestNewPayloadVersion_%s", test.name), func(t *testing.T) { 588 config.EcotoneTime = &test.ecotoneTime 589 assert.Equal(t, config.NewPayloadVersion(test.payloadTime), test.expectedMethod) 590 }) 591 } 592 } 593 594 func TestGetPayloadVersion(t *testing.T) { 595 config := randConfig() 596 canyonTime := uint64(0) 597 config.CanyonTime = &canyonTime 598 tests := []struct { 599 name string 600 ecotoneTime uint64 601 payloadTime uint64 602 expectedMethod eth.EngineAPIMethod 603 }{ 604 { 605 name: "BeforeEcotone", 606 ecotoneTime: 10, 607 payloadTime: 5, 608 expectedMethod: eth.GetPayloadV2, 609 }, 610 { 611 name: "Ecotone", 612 ecotoneTime: 10, 613 payloadTime: 15, 614 expectedMethod: eth.GetPayloadV3, 615 }, 616 } 617 618 for _, test := range tests { 619 test := test 620 t.Run(fmt.Sprintf("TestGetPayloadVersion_%s", test.name), func(t *testing.T) { 621 config.EcotoneTime = &test.ecotoneTime 622 assert.Equal(t, config.GetPayloadVersion(test.payloadTime), test.expectedMethod) 623 }) 624 } 625 }