diff --git a/kurtosis-devnet/pkg/devnet/cmd/main.go b/kurtosis-devnet/pkg/devnet/cmd/main.go new file mode 100644 index 0000000000000000000000000000000000000000..db205c56f774d057d200e4d047233562ba962567 --- /dev/null +++ b/kurtosis-devnet/pkg/devnet/cmd/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "fmt" + "os" + + "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/devnet/kt" + "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/devnet/manifest" + "github.com/urfave/cli/v2" + "gopkg.in/yaml.v3" +) + +func main() { + app := &cli.App{ + Name: "devnet", + Usage: "Generate Kurtosis parameters from a devnet manifest", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "manifest", + Aliases: []string{"m"}, + Usage: "Path to the manifest YAML file", + Required: true, + }, + &cli.StringFlag{ + Name: "output", + Aliases: []string{"o"}, + Usage: "Path to write the Kurtosis parameters file (default: stdout)", + }, + }, + Action: func(c *cli.Context) error { + // Read manifest file + manifestPath := c.String("manifest") + manifestBytes, err := os.ReadFile(manifestPath) + if err != nil { + return fmt.Errorf("failed to read manifest file: %w", err) + } + + // Parse manifest YAML + var m manifest.Manifest + if err := yaml.Unmarshal(manifestBytes, &m); err != nil { + return fmt.Errorf("failed to parse manifest YAML: %w", err) + } + + // Create visitor and process manifest + visitor := kt.NewKurtosisVisitor() + m.Accept(visitor) + + // Get params and write to file or stdout + params := visitor.GetParams() + paramsBytes, err := yaml.Marshal(params) + if err != nil { + return fmt.Errorf("failed to marshal params: %w", err) + } + + outputPath := c.String("output") + if outputPath != "" { + if err := os.WriteFile(outputPath, paramsBytes, 0644); err != nil { + return fmt.Errorf("failed to write params file: %w", err) + } + } else { + fmt.Print(string(paramsBytes)) + } + + return nil + }, + } + + if err := app.Run(os.Args); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } +} diff --git a/kurtosis-devnet/pkg/devnet/images/repository.go b/kurtosis-devnet/pkg/devnet/images/repository.go new file mode 100644 index 0000000000000000000000000000000000000000..2732d35649fef0de5cf9e6be36496d027d507105 --- /dev/null +++ b/kurtosis-devnet/pkg/devnet/images/repository.go @@ -0,0 +1,45 @@ +package images + +import "fmt" + +// Repository maps component versions to their corresponding Docker image URLs +type Repository struct { + mapping map[string]string +} + +const ( + opLabsToolsRegistry = "us-docker.pkg.dev/oplabs-tools-artifacts/images" + paradigmRegistry = "ghcr.io/paradigmxyz" +) + +// NewRepository creates a new Repository instance with predefined mappings +func NewRepository() *Repository { + return &Repository{ + mapping: map[string]string{ + // OP Labs images + "op-deployer": opLabsToolsRegistry, + "op-geth": opLabsToolsRegistry, + "op-node": opLabsToolsRegistry, + "op-batcher": opLabsToolsRegistry, + "op-proposer": opLabsToolsRegistry, + "op-challenger": opLabsToolsRegistry, + // Paradigm images + "op-reth": paradigmRegistry, + }, + } +} + +// GetImage returns the full Docker image URL for a given component and version +func (r *Repository) GetImage(component string, version string) string { + if imageTemplate, ok := r.mapping[component]; ok { + + if version == "" { + version = "latest" + } + return fmt.Sprintf("%s/%s:%s", imageTemplate, component, version) + } + + // TODO: that's our way to convey that the "default" image should be used. + // We should probably have a more explicit way to do this. + return "" +} diff --git a/kurtosis-devnet/pkg/devnet/kt/params.go b/kurtosis-devnet/pkg/devnet/kt/params.go new file mode 100644 index 0000000000000000000000000000000000000000..2125e08e56cfd65905c56d97fd52f76627629210 --- /dev/null +++ b/kurtosis-devnet/pkg/devnet/kt/params.go @@ -0,0 +1,82 @@ +package kt + +// KurtosisParams represents the top-level Kurtosis configuration +type KurtosisParams struct { + OptimismPackage OptimismPackage `yaml:"optimism_package"` + EthereumPackage EthereumPackage `yaml:"ethereum_package"` +} + +// OptimismPackage represents the Optimism-specific configuration +type OptimismPackage struct { + Chains []ChainConfig `yaml:"chains"` + OpContractDeployerParams OpContractDeployerParams `yaml:"op_contract_deployer_params"` + Persistent bool `yaml:"persistent"` +} + +// ChainConfig represents a single chain configuration +type ChainConfig struct { + Participants []ParticipantConfig `yaml:"participants"` + NetworkParams NetworkParams `yaml:"network_params"` + BatcherParams BatcherParams `yaml:"batcher_params"` + ChallengerParams ChallengerParams `yaml:"challenger_params"` + ProposerParams ProposerParams `yaml:"proposer_params"` +} + +// ParticipantConfig represents a participant in the network +type ParticipantConfig struct { + ElType string `yaml:"el_type"` + ElImage string `yaml:"el_image"` + ClType string `yaml:"cl_type"` + ClImage string `yaml:"cl_image"` + Count int `yaml:"count"` +} + +// TimeOffsets represents a map of time offset values +type TimeOffsets map[string]int + +// NetworkParams represents network-specific parameters +type NetworkParams struct { + Network string `yaml:"network"` + NetworkID string `yaml:"network_id"` + SecondsPerSlot int `yaml:"seconds_per_slot"` + Name string `yaml:"name"` + FundDevAccounts bool `yaml:"fund_dev_accounts"` + TimeOffsets `yaml:",inline"` +} + +// BatcherParams represents batcher-specific parameters +type BatcherParams struct { + Image string `yaml:"image"` +} + +// ChallengerParams represents challenger-specific parameters +type ChallengerParams struct { + Image string `yaml:"image"` + CannonPrestatesURL string `yaml:"cannon_prestates_url,omitempty"` +} + +// ProposerParams represents proposer-specific parameters +type ProposerParams struct { + Image string `yaml:"image"` + GameType int `yaml:"game_type"` + ProposalInterval string `yaml:"proposal_interval"` +} + +// OpContractDeployerParams represents contract deployer parameters +type OpContractDeployerParams struct { + Image string `yaml:"image"` + L1ArtifactsLocator string `yaml:"l1_artifacts_locator"` + L2ArtifactsLocator string `yaml:"l2_artifacts_locator"` +} + +// EthereumPackage represents Ethereum-specific configuration +type EthereumPackage struct { + NetworkParams EthereumNetworkParams `yaml:"network_params"` +} + +// EthereumNetworkParams represents Ethereum network parameters +type EthereumNetworkParams struct { + Preset string `yaml:"preset"` + GenesisDelay int `yaml:"genesis_delay"` + AdditionalPreloadedContracts string `yaml:"additional_preloaded_contracts"` +} diff --git a/kurtosis-devnet/pkg/devnet/kt/visitor.go b/kurtosis-devnet/pkg/devnet/kt/visitor.go new file mode 100644 index 0000000000000000000000000000000000000000..a50e122cefe5710ff941eabefeab03ebf8ff3646 --- /dev/null +++ b/kurtosis-devnet/pkg/devnet/kt/visitor.go @@ -0,0 +1,265 @@ +package kt + +import ( + "strconv" + "strings" + + "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/devnet/images" + "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/devnet/manifest" +) + +const ( + defaultProposalInterval = "10m" + defaultGameType = 1 + defaultPreset = "minimal" + defaultGenesisDelay = 5 + defaultPreloadedContracts = `{ + "0x4e59b44847b379578588920cA78FbF26c0B4956C": { + "balance": "0ETH", + "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", + "storage": {}, + "nonce": "1" + } + }` +) + +// KurtosisVisitor implements the manifest.ManifestVisitor interface +type KurtosisVisitor struct { + params *KurtosisParams + repository *images.Repository + l2Visitor *l2Visitor +} + +// Component visitor for handling component versions +type componentVisitor struct { + name string + version string +} + +// Chain visitor for handling chain configuration +type chainVisitor struct { + name string + id uint64 +} + +// Contracts visitor for handling contract configuration +type contractsVisitor struct { + locator string +} + +// Overrides represents deployment overrides +type Overrides struct { + SecondsPerSlot int `yaml:"seconds_per_slot"` + TimeOffsets `yaml:",inline"` +} + +// Deployment visitor for handling deployment configuration +type deploymentVisitor struct { + deployer *componentVisitor + l1Contracts *contractsVisitor + l2Contracts *contractsVisitor + overrides *Overrides +} + +// L2 visitor for handling L2 configuration +type l2Visitor struct { + components map[string]*componentVisitor + deployment *deploymentVisitor + chains []*chainVisitor +} + +// NewKurtosisVisitor creates a new KurtosisVisitor +func NewKurtosisVisitor() *KurtosisVisitor { + return &KurtosisVisitor{ + params: &KurtosisParams{ + OptimismPackage: OptimismPackage{ + Chains: make([]ChainConfig, 0), + Persistent: false, + }, + EthereumPackage: EthereumPackage{ + NetworkParams: EthereumNetworkParams{ + Preset: defaultPreset, + GenesisDelay: defaultGenesisDelay, + AdditionalPreloadedContracts: defaultPreloadedContracts, + }, + }, + }, + repository: images.NewRepository(), + } +} + +func (v *KurtosisVisitor) VisitName(name string) {} + +func (v *KurtosisVisitor) VisitType(manifestType string) {} + +func (v *KurtosisVisitor) VisitL1() manifest.ChainVisitor { + return &chainVisitor{} +} + +func (v *KurtosisVisitor) VisitL2() manifest.L2Visitor { + v.l2Visitor = &l2Visitor{ + components: make(map[string]*componentVisitor), + deployment: &deploymentVisitor{ + deployer: &componentVisitor{}, + l1Contracts: &contractsVisitor{}, + l2Contracts: &contractsVisitor{}, + overrides: &Overrides{ + TimeOffsets: make(TimeOffsets), + }, + }, + chains: make([]*chainVisitor, 0), + } + return v.l2Visitor +} + +// Component visitor implementation +func (v *componentVisitor) VisitVersion(version string) { + // Strip the component name from the version string + parts := strings.SplitN(version, "/", 2) + if len(parts) == 2 { + v.version = parts[1] + } else { + v.version = version + } +} + +// Chain visitor implementation +func (v *chainVisitor) VisitName(name string) { + v.name = name +} + +func (v *chainVisitor) VisitID(id uint64) { + // TODO: this is horrible but unfortunately the funding script breaks for + // chain IDs larger than 32 bits. + v.id = id & 0xFFFFFFFF +} + +// Contracts visitor implementation +func (v *contractsVisitor) VisitVersion(version string) { + if v.locator == "" { + v.locator = "tag://" + version + } +} + +func (v *contractsVisitor) VisitLocator(locator string) { + v.locator = locator +} + +// Deployment visitor implementation +func (v *deploymentVisitor) VisitDeployer() manifest.ComponentVisitor { + return v.deployer +} + +func (v *deploymentVisitor) VisitL1Contracts() manifest.ContractsVisitor { + return v.l1Contracts +} + +func (v *deploymentVisitor) VisitL2Contracts() manifest.ContractsVisitor { + return v.l2Contracts +} + +func (v *deploymentVisitor) VisitOverride(key string, value interface{}) { + if key == "seconds_per_slot" { + if intValue, ok := value.(int); ok { + v.overrides.SecondsPerSlot = intValue + } + } else if strings.HasSuffix(key, "_time_offset") { + if intValue, ok := value.(int); ok { + v.overrides.TimeOffsets[key] = intValue + } + } +} + +// L2 visitor implementation +func (v *l2Visitor) VisitL2Component(name string) manifest.ComponentVisitor { + comp := &componentVisitor{name: name} + v.components[name] = comp + return comp +} + +func (v *l2Visitor) VisitL2Deployment() manifest.DeploymentVisitor { + return v.deployment +} + +func (v *l2Visitor) VisitL2Chain(idx int) manifest.ChainVisitor { + chain := &chainVisitor{} + if idx >= len(v.chains) { + v.chains = append(v.chains, chain) + } else { + v.chains[idx] = chain + } + return chain +} + +// GetParams returns the generated Kurtosis parameters +func (v *KurtosisVisitor) GetParams() *KurtosisParams { + if v.l2Visitor != nil { + v.BuildKurtosisParams(v.l2Visitor) + } + return v.params +} + +// getComponentVersion returns the version for a component, or empty string if not found +func (l2 *l2Visitor) getComponentVersion(name string) string { + if comp, ok := l2.components[name]; ok { + return comp.version + } + return "" +} + +// getComponentImage returns the image for a component, or empty string if component doesn't exist +func (v *KurtosisVisitor) getComponentImage(l2 *l2Visitor, name string) string { + if _, ok := l2.components[name]; ok { + return v.repository.GetImage(name, l2.getComponentVersion(name)) + } + return "" +} + +// BuildKurtosisParams builds the final Kurtosis parameters from the collected visitor data +func (v *KurtosisVisitor) BuildKurtosisParams(l2 *l2Visitor) { + // Set deployer params + v.params.OptimismPackage.OpContractDeployerParams = OpContractDeployerParams{ + Image: v.repository.GetImage("op-deployer", l2.deployment.deployer.version), + L1ArtifactsLocator: l2.deployment.l1Contracts.locator, + L2ArtifactsLocator: l2.deployment.l2Contracts.locator, + } + + // Build chain configs + for _, chain := range l2.chains { + // Create network params with embedded map + networkParams := NetworkParams{ + Network: "kurtosis", + NetworkID: strconv.FormatUint(chain.id, 10), + SecondsPerSlot: l2.deployment.overrides.SecondsPerSlot, + Name: chain.name, + FundDevAccounts: true, + TimeOffsets: l2.deployment.overrides.TimeOffsets, + } + + chainConfig := ChainConfig{ + Participants: []ParticipantConfig{ + { + ElType: "op-geth", + ElImage: v.getComponentImage(l2, "op-geth"), + ClType: "op-node", + ClImage: v.getComponentImage(l2, "op-node"), + Count: 1, + }, + }, + NetworkParams: networkParams, + BatcherParams: BatcherParams{ + Image: v.getComponentImage(l2, "op-batcher"), + }, + ChallengerParams: ChallengerParams{ + Image: v.getComponentImage(l2, "op-challenger"), + }, + ProposerParams: ProposerParams{ + Image: v.getComponentImage(l2, "op-proposer"), + GameType: defaultGameType, + ProposalInterval: defaultProposalInterval, + }, + } + + v.params.OptimismPackage.Chains = append(v.params.OptimismPackage.Chains, chainConfig) + } +} diff --git a/kurtosis-devnet/pkg/devnet/kt/visitor_test.go b/kurtosis-devnet/pkg/devnet/kt/visitor_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7d905bfe94fc87e07b069d016c1c67abf9ac77c2 --- /dev/null +++ b/kurtosis-devnet/pkg/devnet/kt/visitor_test.go @@ -0,0 +1,121 @@ +package kt + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/devnet/manifest" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestKurtosisVisitor_TransformsManifest(t *testing.T) { + input := ` +name: alpaca +type: alphanet +l1: + name: sepolia + chain_id: 11155111 +l2: + deployment: + op-deployer: + version: op-deployer/v0.0.11 + l1-contracts: + locator: https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-c3f2e2adbd52a93c2c08cab018cd637a4e203db53034e59c6c139c76b4297953.tar.gz + version: 984bae9146398a2997ec13757bfe2438ca8f92eb + l2-contracts: + version: op-contracts/v.1.7.0-beta.1+l2-contracts + overrides: + seconds_per_slot: 2 + fjord_time_offset: 0 + granite_time_offset: 0 + holocene_time_offset: 0 + components: + op-node: + version: op-node/v1.10.2 + op-geth: + version: op-geth/v1.101411.4-rc.4 + op-reth: + version: op-reth/v1.1.5 + op-proposer: + version: op-proposer/v1.10.0-rc.2 + op-batcher: + version: op-batcher/v1.10.0 + op-challenger: + version: op-challenger/v1.3.1-rc.4 + chains: + - name: alpaca-0 + chain_id: 11155111100000 +` + + // Then the output should match the expected YAML structure + expected := KurtosisParams{ + OptimismPackage: OptimismPackage{ + Chains: []ChainConfig{ + { + Participants: []ParticipantConfig{ + { + ElType: "op-geth", + ElImage: "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101411.4-rc.4", + ClType: "op-node", + ClImage: "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:v1.10.2", + Count: 1, + }, + }, + NetworkParams: NetworkParams{ + Network: "kurtosis", + NetworkID: "11155111100000", + SecondsPerSlot: 2, + Name: "alpaca-0", + FundDevAccounts: true, + TimeOffsets: TimeOffsets{ + "fjord_time_offset": 0, + "granite_time_offset": 0, + "holocene_time_offset": 0, + }, + }, + BatcherParams: BatcherParams{ + Image: "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-batcher:v1.10.0", + }, + ChallengerParams: ChallengerParams{ + Image: "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:v1.3.1-rc.4", + CannonPrestatesURL: "", + }, + ProposerParams: ProposerParams{ + Image: "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-proposer:v1.10.0-rc.2", + GameType: 1, + ProposalInterval: "10m", + }, + }, + }, + OpContractDeployerParams: OpContractDeployerParams{ + Image: "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-deployer:v0.0.11", + L1ArtifactsLocator: "https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-c3f2e2adbd52a93c2c08cab018cd637a4e203db53034e59c6c139c76b4297953.tar.gz", + L2ArtifactsLocator: "op-contracts/v.1.7.0-beta.1+l2-contracts", + }, + Persistent: false, + }, + EthereumPackage: EthereumPackage{ + NetworkParams: EthereumNetworkParams{ + Preset: "minimal", + GenesisDelay: 5, + AdditionalPreloadedContracts: defaultPreloadedContracts, + }, + }, + } + + // Convert the input to a manifest + var manifest manifest.Manifest + err := yaml.Unmarshal([]byte(input), &manifest) + require.NoError(t, err) + + // Create visitor and have manifest accept it + visitor := NewKurtosisVisitor() + manifest.Accept(visitor) + + // Get the generated params + actual := *visitor.GetParams() + + // Compare the actual and expected params + require.Equal(t, expected, actual, "Generated params should match expected params") + +} diff --git a/kurtosis-devnet/pkg/devnet/manifest/acceptor.go b/kurtosis-devnet/pkg/devnet/manifest/acceptor.go new file mode 100644 index 0000000000000000000000000000000000000000..b7873c6f607145b4f5bd5a569b4998694010a081 --- /dev/null +++ b/kurtosis-devnet/pkg/devnet/manifest/acceptor.go @@ -0,0 +1,25 @@ +package manifest + +type ManifestAcceptor interface { + Accept(visitor ManifestVisitor) +} + +type ChainAcceptor interface { + Accept(visitor ChainVisitor) +} + +type L2Acceptor interface { + Accept(visitor L2Visitor) +} + +type DeploymentAcceptor interface { + Accept(visitor DeploymentVisitor) +} + +type ContractsAcceptor interface { + Accept(visitor ContractsVisitor) +} + +type ComponentAcceptor interface { + Accept(visitor ComponentVisitor) +} diff --git a/kurtosis-devnet/pkg/devnet/manifest/manifest.go b/kurtosis-devnet/pkg/devnet/manifest/manifest.go new file mode 100644 index 0000000000000000000000000000000000000000..65d9e5f7da6235869a78ebe8f2e851ab2e31ac32 --- /dev/null +++ b/kurtosis-devnet/pkg/devnet/manifest/manifest.go @@ -0,0 +1,104 @@ +package manifest + +// L1Config represents L1 configuration +type L1Config struct { + Name string `yaml:"name"` + ChainID uint64 `yaml:"chain_id"` +} + +func (c *L1Config) Accept(visitor ChainVisitor) { + visitor.VisitName(c.Name) + visitor.VisitID(c.ChainID) +} + +var _ ChainAcceptor = (*L1Config)(nil) + +type Component struct { + Version string `yaml:"version"` +} + +func (c *Component) Accept(visitor ComponentVisitor) { + visitor.VisitVersion(c.Version) +} + +var _ ComponentAcceptor = (*Component)(nil) + +type Contracts struct { + Version string `yaml:"version"` + Locator string `yaml:"locator"` +} + +func (c *Contracts) Accept(visitor ContractsVisitor) { + visitor.VisitLocator(c.Locator) + visitor.VisitVersion(c.Version) +} + +var _ ContractsAcceptor = (*Contracts)(nil) + +// L2Deployment represents deployment configuration +type L2Deployment struct { + OpDeployer *Component `yaml:"op-deployer"` + L1Contracts *Contracts `yaml:"l1-contracts"` + L2Contracts *Contracts `yaml:"l2-contracts"` + Overrides map[string]interface{} `yaml:"overrides"` +} + +func (d *L2Deployment) Accept(visitor DeploymentVisitor) { + d.OpDeployer.Accept(visitor.VisitDeployer()) + d.L1Contracts.Accept(visitor.VisitL1Contracts()) + d.L2Contracts.Accept(visitor.VisitL2Contracts()) + for key, value := range d.Overrides { + visitor.VisitOverride(key, value) + } +} + +var _ DeploymentAcceptor = (*L2Deployment)(nil) + +// L2Chain represents an L2 chain configuration +type L2Chain struct { + Name string `yaml:"name"` + ChainID uint64 `yaml:"chain_id"` +} + +func (c *L2Chain) Accept(visitor ChainVisitor) { + visitor.VisitName(c.Name) + visitor.VisitID(c.ChainID) +} + +var _ ChainAcceptor = (*L2Chain)(nil) + +// L2Config represents L2 configuration +type L2Config struct { + Deployment *L2Deployment `yaml:"deployment"` + Components map[string]*Component `yaml:"components"` + Chains []*L2Chain `yaml:"chains"` +} + +func (c *L2Config) Accept(visitor L2Visitor) { + for name, component := range c.Components { + component.Accept(visitor.VisitL2Component(name)) + } + for i, chain := range c.Chains { + chain.Accept(visitor.VisitL2Chain(i)) + } + c.Deployment.Accept(visitor.VisitL2Deployment()) +} + +var _ L2Acceptor = (*L2Config)(nil) + +// Manifest represents the top-level manifest configuration +type Manifest struct { + Name string `yaml:"name"` + Type string `yaml:"type"` + L1 *L1Config `yaml:"l1"` + L2 *L2Config `yaml:"l2"` +} + +func (m *Manifest) Accept(visitor ManifestVisitor) { + visitor.VisitName(m.Name) + visitor.VisitType(m.Type) + m.L1.Accept(visitor.VisitL1()) + m.L2.Accept(visitor.VisitL2()) +} + +var _ ManifestAcceptor = (*Manifest)(nil) diff --git a/kurtosis-devnet/pkg/devnet/manifest/visitor.go b/kurtosis-devnet/pkg/devnet/manifest/visitor.go new file mode 100644 index 0000000000000000000000000000000000000000..c6a3861e33dbce23742719843e2ecc3c62869371 --- /dev/null +++ b/kurtosis-devnet/pkg/devnet/manifest/visitor.go @@ -0,0 +1,35 @@ +package manifest + +type ManifestVisitor interface { + VisitName(name string) + VisitType(manifestType string) + VisitL1() ChainVisitor + VisitL2() L2Visitor +} + +type L2Visitor interface { + VisitL2Component(name string) ComponentVisitor + VisitL2Deployment() DeploymentVisitor + VisitL2Chain(int) ChainVisitor +} + +type ComponentVisitor interface { + VisitVersion(version string) +} + +type DeploymentVisitor interface { + VisitDeployer() ComponentVisitor + VisitL1Contracts() ContractsVisitor + VisitL2Contracts() ContractsVisitor + VisitOverride(string, interface{}) +} + +type ContractsVisitor interface { + VisitVersion(version string) + VisitLocator(locator string) +} + +type ChainVisitor interface { + VisitName(name string) + VisitID(id uint64) +}