diff --git a/.circleci/continue/main.yml b/.circleci/continue/main.yml index 42df66bd9b8e2..aabb4c2a505ee 100644 --- a/.circleci/continue/main.yml +++ b/.circleci/continue/main.yml @@ -2802,7 +2802,7 @@ jobs: - run: name: Collect devnet metrics for op-acceptance-tests command: | - ./devnet-sdk/scripts/metrics-collect-authorship.sh op-acceptance-tests/tests > .metrics--authorship--op-acceptance-tests + ./op-acceptance-tests/scripts/metrics-collect-authorship.sh op-acceptance-tests/tests > .metrics--authorship--op-acceptance-tests echo "Wrote file .metrics--authorship--op-acceptance-tests" - gcp-cli/install - gcp-oidc-authenticate: diff --git a/AGENTS.md b/AGENTS.md index 2857cddef15bf..7d7055bf0b98b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,8 +48,6 @@ The OP Stack includes significant Rust implementations: ### Development and Testing Infrastructure -- **devnet-sdk**: Toolkit for devnet interactions -- **kurtosis-devnet**: Kurtosis-based devnet environment (DEPRECATED) - **op-e2e**: End-to-end testing framework - **op-acceptance-tests**: Acceptance test suite diff --git a/Makefile b/Makefile index bfe1871860484..ea34470a152e5 100644 --- a/Makefile +++ b/Makefile @@ -245,8 +245,6 @@ TEST_PKGS := \ ./op-e2e/actions/upgrades \ ./packages/contracts-bedrock/scripts/checks/... \ ./op-dripper/... \ - ./devnet-sdk/... \ - ./kurtosis-devnet/... \ ./op-devstack/... \ ./op-deployer/pkg/deployer/artifacts/... \ ./op-deployer/pkg/deployer/broadcaster/... \ diff --git a/README.md b/README.md index bdd480d522e01..ee813779d7bd0 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,7 @@ The Optimism Immunefi program offers up to $2,000,042 for in-scope critical vuln
 ├── cannon: Onchain MIPS instruction emulator for fault proofs
-├── devnet-sdk: Comprehensive toolkit for standardized devnet interactions
 ├── docs: A collection of documents including audits and post-mortems
-├── kurtosis-devnet: OP-Stack Kurtosis devnet
 ├── op-acceptance-tests: Acceptance tests and configuration for OP Stack
 ├── op-alt-da: Alternative Data Availability mode (beta)
 ├── op-batcher: L2-Batch Submitter, submits bundles of batches to L1
diff --git a/devnet-sdk/book/.gitignore b/devnet-sdk/book/.gitignore
deleted file mode 100644
index 7585238efedfc..0000000000000
--- a/devnet-sdk/book/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-book
diff --git a/devnet-sdk/book/book.toml b/devnet-sdk/book/book.toml
deleted file mode 100644
index bde35e66b7da0..0000000000000
--- a/devnet-sdk/book/book.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[book]
-authors = ["Optimism Contributors"]
-language = "en"
-multilingual = false
-src = "src"
-title = "Devnet SDK Book"
-
-[output.html]
-site-url = "/devnet-sdk/"
-git-repository-url = "https://github.com/ethereum-optimism/optimism/tree/develop/devnet-sdk/book"
-edit-url-template = "https://github.com/ethereum-optimism/optimism/tree/develop/devnet-sdk/book/{path}"
-additional-css = ["custom.css", "theme/css/footer.css"]
-additional-js = ["theme/js/footer.js"]
diff --git a/devnet-sdk/book/custom.css b/devnet-sdk/book/custom.css
deleted file mode 100644
index 7c94143752af4..0000000000000
--- a/devnet-sdk/book/custom.css
+++ /dev/null
@@ -1,5 +0,0 @@
-.content main {
-  max-width: 85%;
-  margin-left: auto;
-  margin-right: auto;
-}
diff --git a/devnet-sdk/book/src/README.md b/devnet-sdk/book/src/README.md
deleted file mode 100644
index 9947175682465..0000000000000
--- a/devnet-sdk/book/src/README.md
+++ /dev/null
@@ -1,54 +0,0 @@
-> ⚠️ **UNDER HEAVY DEVELOPMENT** ⚠️
->
-> This documentation is actively being developed and may change frequently.
-
-# Introduction
-
-# Devnet SDK
-
-The Devnet SDK is a comprehensive toolkit designed to standardize and simplify interactions with Optimism devnets. It provides a robust set of tools and interfaces for deploying, managing, and testing Optimism networks in development environments.
-
-## Core Components
-
-### 1. Devnet Descriptors
-
-The descriptors package defines a standard interface for describing and interacting with devnets. It provides:
-
-- Structured representation of devnet environments including L1 and L2 chains
-- Service discovery and endpoint management
-- Wallet and address management
-- Standardized configuration for chain components (nodes, services, endpoints)
-
-### 2. Shell Integration
-
-The shell package provides a preconfigured shell environment for interacting with devnets. For example, you can quickly:
-
-- Launch a shell with all environment variables set and run commands like `cast balance 
` that automatically use the correct RPC endpoints -- Access chain-specific configuration like JWT secrets and contract addresses - -This makes it easy to interact with your devnet without manually configuring tools or managing connection details. - -### 3. System Interface - -The system package provides a devnet-agnostic programmatic interface, constructed for example from the descriptors above, for interacting with Optimism networks. Key features include: - -- Unified interface for L1 and L2 chain interactions -- Transaction management and processing -- Wallet management and operations -- Contract interaction capabilities -- Interoperability features between different L2 chains - -Core interfaces include: -- `System`: Represents a complete Optimism system with L1 and L2 chains -- `Chain`: Provides access to chain-specific operations -- `Wallet`: Manages accounts, transactions, and signing operations -- `Transaction`: Handles transaction creation and processing - -### 4. Testing Framework - -The testing package provides a comprehensive framework for testing devnet deployments: - -- Standardized testing utilities -- System test capabilities -- Integration test helpers -- Test fixtures and utilities diff --git a/devnet-sdk/book/src/SUMMARY.md b/devnet-sdk/book/src/SUMMARY.md deleted file mode 100644 index fb46d91bb1094..0000000000000 --- a/devnet-sdk/book/src/SUMMARY.md +++ /dev/null @@ -1,15 +0,0 @@ -# Summary - -[Introduction](README.md) - -# Usage - -- [Descriptors Documentation](./descriptors.md) -- [Shell Integration Guide](./shell.md) -- [System Interface Reference](./system.md) -- [Testing Framework Guide](./testing.md) - -# DSL - -- [Introduction](dsl/intro.md) -- [Style Guide](dsl/style_guide.md) diff --git a/devnet-sdk/book/src/descriptors.md b/devnet-sdk/book/src/descriptors.md deleted file mode 100644 index 20dc1e21a460c..0000000000000 --- a/devnet-sdk/book/src/descriptors.md +++ /dev/null @@ -1,142 +0,0 @@ -# Devnet Descriptors - -The devnet descriptor is a standardized format that describes the complete topology and configuration of an Optimism devnet deployment. This standard serves as a bridge between different devnet implementations and the higher-level tooling provided by the devnet-sdk. - -## Universal Descriptor Format - -Both `kurtosis-devnet` and `netchef` emit the same descriptor format, despite being completely different devnet implementations: - -- **kurtosis-devnet**: Uses Kurtosis to orchestrate containerized devnet deployments -- **netchef**: Provides a lightweight, local devnet deployment - -This standardization enables a powerful ecosystem where tools can be built independently of the underlying devnet implementation. - -## Descriptor Structure - -A devnet descriptor provides a complete view of a running devnet: - -```json -{ - "l1": { - "name": "l1", - "id": "900", - "services": { - "geth": { - "name": "geth", - "endpoints": { - "rpc": { - "host": "localhost", - "port": 8545 - } - } - } - }, - "nodes": [...], - "addresses": { - "deployer": "0x...", - "admin": "0x..." - }, - "wallets": { - "admin": { - "address": "0x...", - "private_key": "0x..." - } - } - }, - "l2": [ - { - "name": "op-sepolia", - "id": "11155420", - "services": {...}, - "nodes": [...], - "addresses": {...}, - "wallets": {...} - } - ], - "features": ["eip1559", "shanghai"] -} -``` - -## Enabling Devnet-Agnostic Tooling - -The power of the descriptor format lies in its ability to make any compliant devnet implementation immediately accessible to the entire devnet-sdk toolset: - -1. **Universal Interface**: Any devnet that can emit this descriptor format can be managed through devnet-sdk's tools -2. **Automatic Integration**: New devnet implementations only need to implement the descriptor format to gain access to: - - System interface for chain interaction - - Testing framework - - Shell integration tools - - Wallet management - - Transaction processing - -## Benefits - -This standardization provides several key advantages: - -- **Portability**: Tools built against the devnet-sdk work with any compliant devnet implementation -- **Consistency**: Developers get the same experience regardless of the underlying devnet -- **Extensibility**: New devnet implementations can focus on deployment mechanics while leveraging existing tooling -- **Interoperability**: Tools can be built that work across different devnet implementations - -## Implementation Requirements - -To make a devnet implementation compatible with devnet-sdk, it needs to: - -1. Provide a mechanism to output the descriptor (typically as JSON) -2. Ensure all required services and endpoints are properly described - -Once these requirements are met, the devnet automatically gains access to the full suite of devnet-sdk capabilities. - -## Status - -The descriptor format is currently in active development, particularly regarding endpoint specifications: - -### Endpoint Requirements - -- **Current State**: The format does not strictly specify which endpoints must be included in a compliant descriptor -- **Minimum Known Requirements**: - - RPC endpoints are essential for basic chain interaction - - Other endpoints may be optional depending on use case - -### Implementation Notes - -- `kurtosis-devnet` currently outputs all service endpoints by default, including many that may not be necessary for testing -- Other devnet implementations can be more selective about which endpoints they expose -- Different testing scenarios may require different sets of endpoints - -### Future Development - -We plan to develop more specific recommendations for: -- Required vs optional endpoints -- Standard endpoint naming conventions -- Service-specific endpoint requirements -- Best practices for endpoint exposure - -Until these specifications are finalized, devnet implementations should: -1. Always include RPC endpoints -2. Document which additional endpoints they expose -3. Consider their specific use cases when deciding which endpoints to include - -## Example Usage - -Here's how a tool might use the descriptor to interact with any compliant devnet: - -```go -// Load descriptor from any compliant devnet -descriptor, err := descriptors.Load("devnet.json") -if err != nil { - log.Fatal(err) -} - -// Use the descriptor with devnet-sdk tools -system, err := system.FromDescriptor(descriptor) -if err != nil { - log.Fatal(err) -} - -// Now you can use all devnet-sdk features -l1 := system.L1() -l2 := system.L2(descriptor.L2[0].ID) -``` - -This standardization enables a rich ecosystem of tools that work consistently across different devnet implementations, making development and testing more efficient and reliable. diff --git a/devnet-sdk/book/src/dsl/intro.md b/devnet-sdk/book/src/dsl/intro.md deleted file mode 100644 index bc6b3b4757f74..0000000000000 --- a/devnet-sdk/book/src/dsl/intro.md +++ /dev/null @@ -1,41 +0,0 @@ -# Introduction - -The devnet-sdk DSL is a high level test library, specifically designed for end to end / acceptance testing of the -OP Stack. It aims to make the development and maintenance of whole system tests faster and easier. - -The high level API helps make the actual test read in a more declarative style and separate the technical details of how -an action is actually performed. The intended result is that tests express the requirements, while the DSL provides the -technical details of how those requirements are met. This ensures that as the technical details change, the DSL can -be updated rather than requiring that each test be updated individual - significantly reducing the maintenance cost for -a large test suite. Similarly, if there is flakiness in tests, it can often be solved by improving the DSL to -properly wait for pre or post conditions or automatically perform required setup steps and that fix is automatically -applied everywhere, including tests added in the future. - -## Guiding Principles - -These guiding principles allow the test suite to evolve and grow over time in a way that ensures the tests are -maintainable and continue to be easy to write. With multiple different teams contributing to tests, over a long time -period, shared principles are required to avoid many divergent approaches and frameworks emerging which increase the -cognitive load for developers writing tests and increase the maintenance costs for existing tests. - -### Keep It Simple - -Avoid attempting to make the DSL read like plain English. This is a domain-specific language and the domain experts are -actually the test developers, not non-technical users. Each statement should clearly describe what it is trying to do, -but does not need to read like an English sentence. - -Bias very strongly towards making the tests simpler, even if the DSL implementation then needs to be more complex. -Complexity in tests will be duplicated for each test case whereas complexity in the DSL is more centralised and is -encapsulated so it is much less likely to be a distraction. - -### Consistency - -The "language" of the DSL emerges by being consistent in the structures and naming used for things. Take the time to -refactor things to ensure that the same name is used consistently for a concept right across the DSL. - -Bias towards following established patterns rather than doing something new. While introducing a new pattern might make -things cleaner in a particular test, it introduces additional cognitive load for people to understand when working with -the tests. It is usually (but not always) better to preserve consistency than to have a marginally nicer solution for -one specific scenario. - -The [style guide](./style_guide.md) defines a set of common patterns and guidelines that should be followed. diff --git a/devnet-sdk/book/src/dsl/style_guide.md b/devnet-sdk/book/src/dsl/style_guide.md deleted file mode 100644 index 3095892ddb9fa..0000000000000 --- a/devnet-sdk/book/src/dsl/style_guide.md +++ /dev/null @@ -1,133 +0,0 @@ -# DSL Style Guide - -This style guide outlines common patterns and anti-patterns used by the testing DSL. Following this guide not only -improves consistency, it helps keep the separation of requirements (in test files) from implementation details (in DSL -implementation), which in turn ensures tests are maintainable even as the number of tests keeps increasing over time. - -## Entry Points - -What are the key entry points for the system? Nodes/services, users, contracts?? - -## Action Methods - -Methods that perform actions will typically have three steps: - -1. Check (and if needed, wait) for any required preconditions -2. Perform the action, allowing components to fully process the effects of it -3. Assert that the action completed. These are intended to be a sanity check to ensure tests fail fast if something - doesn't work as expected. Options may be provided to perform more detailed or specific assertions - -## Verification Methods - -Verification methods in the DSL provide additional assertions about the state of the system, beyond the minimal -assertions performed by action methods. - -Verification methods should include any required waiting or retrying. - -Verification methods should generally only be used in tests to assert the specific behaviours the test is covering. -Avoid adding additional verification steps in a test to assert that setup actions were performed correctly - such -assertions should be built into the action methods. While sanity checking setup can be useful, adding additional -verification method calls into tests makes it harder to see what the test is actually intending to cover and increases -the number of places that need to be updated if the behaviour being verified changes in the future. - -### Avoid Getter Methods - -The DSL generally avoids exposing methods that return data from the system state. Instead verification methods are -exposed which combine the fetching and assertion of the data. This allows the DSL to handle any waiting or retrying -that may be necessary (or become necessary). This avoids a common source of flakiness where tests assume an asynchronous -operation will have completed instead of explicitly waiting for the expected final state. - - -```go -// Avoid: the balance of an account is data from the system which changes over time -block := node.GetBalance(user) - -// Good: use a verification method -node.VerifyBalance(user, 10 * constants.Eth) - -// Better? Select the entry point to be as declarative as possible -user.VerifyBalacne(10 * constants.Eth) // implementation could verify balance on all nodes automatically -``` - - -Note however that this doesn't mean that DSL methods never return anything. While returning raw data is avoided, -returning objects that represent something in the system is ok. e.g. - -```go -claim := game.RootClaim() - -// Waits for op-challenger to counter the root claim and returns a value representing that counter claim -// which can expose further verification or action methods. -counter := claim.VerifyCountered() -counter.VerifyClaimant(honestChallenger) -counter.Attack() -``` - -## Method Arguments - -Required inputs to methods are specified as normal parameters, so type checking enforces their presence. - -Optional inputs to methods are specified by a config struct and accept a vararg of functions that can update that struct. -This is roughly inline with the typical opts pattern in Golang but with significantly reduced boilerplate code since -so many methods will define their own config. With* methods are only provided for the most common optional args and -tests will normally supply a custom function that sets all the optional values they need at once. - -## Logging - -Include logging to indicate what the test is doing within the DSL methods. - -Methods that wait should log what they are waiting for and the current state of the system on each poll cycle. - -## No Sleeps - -Neither tests nor DSL code should use hard coded sleeps. CI systems tend to be under heavy and unpredictable load so -short sleep times lead to flaky tests when the system is slower than expected. Long sleeps waste time, causing test runs -to be too slow. By using a waiter pattern, a long timeout can be applied to avoid flakiness, while allowing the test to -progress quickly once the condition is met. - -```go -// Avoid: arbitrary delays -node.DoSomething() -time.Sleep(2 * time.Minute) -node.VerifyResult() - -// Good: build wait/retry loops into the testlib method -node.DoSomething() -node.VerifyResult() // Automatically waits -``` - -## Test Smells - -"Smells" are patterns that indicate there may be a problem. They aren't hard rules, but indicate that something may not -be right and the developer should take a little time to consider if there are better alternatives. - -### Comment and Code Block - -Where possible, test code should be self-explanatory with testlib method calls that are high level enough to not need -comments explaining what they do in the test. When comments are required to explain simple setup, it's an indication -that the testlib method is either poorly named or that a higher level method should be introduced. - -```go -// Smelly: Test code is far too low level and needs to be explained with a comment -// Deploy test contract -storeProgram := program.New().Sstore(0, 0xbeef).Bytes() -walletv2, err := system.NewWalletV2FromWalletAndChain(ctx, wallet, l2Chain) -require.NoError(t, err) -storeAddr, err := DeployProgram(ctx, walletv2, storeProgram) -require.NoError(t, err) -code, err := l2Client.CodeAt(ctx, storeAddr, nil) -require.NoError(t, err) -require.NotEmpty(t, code, "Store contract not deployed") -require.Equal(t, code, storeProgram, "Store contract code incorrect") - -// Good: Introduce a testlib method to encapsulate the detail and keep the test high level -contract := contracts.SStoreContract.Deploy(l2Node, 0xbeef) -``` - -However, not all comments are bad: - -```go -// Good: Explain the calculations behind specific numbers -// operatorFeeCharged = gasUsed * operatorFeeScalar == 1000 * 5 == 5000 -tx.VerifyOperatorFeeCharged(5000) -``` diff --git a/devnet-sdk/book/src/shell.md b/devnet-sdk/book/src/shell.md deleted file mode 100644 index f0ad9cad1dc88..0000000000000 --- a/devnet-sdk/book/src/shell.md +++ /dev/null @@ -1,81 +0,0 @@ -# Shell Integration - -The devnet-sdk provides powerful shell integration capabilities that allow developers to "enter" a devnet environment, making interactions with the network more intuitive and streamlined. - -## Devnet Shell Environment - -Using a devnet's descriptor, we can create a shell environment that is automatically configured with all the necessary context to interact with the devnet: - -```bash -# Enter a shell configured for your devnet -devnet-sdk shell --descriptor path/to/devnet.json -``` - -### Automatic Configuration - -When you enter a devnet shell, the environment is automatically configured with: - -- Environment variables for RPC endpoints -- JWT authentication tokens where required -- Named wallet addresses -- Chain IDs -- Other devnet-specific configuration - -### Simplified Tool Usage - -This automatic configuration enables seamless use of Ethereum development tools without explicit endpoint configuration: - -```bash -# Without devnet shell -cast balance 0x123... --rpc-url http://localhost:8545 --jwt-secret /path/to/jwt - -# With devnet shell -cast balance 0x123... # RPC and JWT automatically configured -``` - -## Supported Tools - -The shell environment enhances the experience with various Ethereum development tools: - -- `cast`: For sending transactions and querying state - -## Environment Variables - -The shell automatically sets up standard Ethereum environment variables based on the descriptor: - -```bash -# Chain enpointpoit -export ETH_RPC_URL=... -export ETH_JWT_SECRET=... -``` - -## Usage Examples - -```bash -# Enter devnet shell -go run devnet-sdk/shell/cmd/enter/main.go --descriptor devnet.json --chain ... - -# Now you can use tools directly -cast block latest - -# Exit the shell -exit -``` - -## Benefits - -- **Simplified Workflow**: No need to manually configure RPC endpoints or authentication -- **Consistent Environment**: Same configuration across all tools and commands -- **Reduced Error Risk**: Eliminates misconfigurations and copy-paste errors -- **Context Awareness**: Shell knows about all chains and services in your devnet - -## Implementation Details - -The shell integration: - -1. Reads the descriptor file -2. Sets up environment variables based on the descriptor content -3. Creates a new shell session with the configured environment -4. Maintains the environment until you exit the shell - -This feature makes it significantly easier to work with devnets by removing the need to manually manage connection details and authentication tokens. diff --git a/devnet-sdk/book/src/system.md b/devnet-sdk/book/src/system.md deleted file mode 100644 index 7ef3bd10352c5..0000000000000 --- a/devnet-sdk/book/src/system.md +++ /dev/null @@ -1,199 +0,0 @@ -# System Interfaces - -The devnet-sdk provides a set of Go interfaces that abstract away the specifics of devnet deployments, enabling automation solutions to work consistently across different deployment types and implementations. - -## Core Philosophy - -While the Descriptor interfaces provide a common way to describe actual devnet deployments (like production-like or Kurtosis-based deployments), the System interfaces operate at a higher level of abstraction. They are designed to support both real deployments and lightweight testing environments. - -The key principles are: - -- **Deployment-Agnostic Automation**: Code written against these interfaces works with any implementation - from full deployments described by Descriptors to in-memory stacks or completely fake environments -- **Flexible Testing Options**: Enables testing against: - - Complete devnet deployments - - Partial mock implementations - - Fully simulated environments -- **One-Way Abstraction**: While Descriptors can be converted into System interfaces, System interfaces can represent additional constructs beyond what Descriptors describe -- **Implementation Freedom**: New deployment types or testing environments can be added without modifying existing automation code - -## Interface Purity - -A critical design principle of these interfaces is their **purity**. This means that interfaces: - -1. **Only Reference Other Pure Interfaces**: Each interface method can only return or accept: - - Other pure interfaces from this package - - Simple data objects that can be fully instantiated - - Standard Go types and primitives - -2. **Avoid Backend-Specific Types**: The interfaces never expose types that would create dependencies on specific implementations: - ```go - // BAD: Creates dependency on specific client implementation - func (c Chain) GetNodeClient() *specific.NodeClient - - // GOOD: Returns pure interface that can be implemented by any backend - func (c Chain) Client() (ChainClient, error) - ``` - -3. **Use Generic Data Types**: When complex data structures are needed, they are defined as pure data objects: - ```go - // Pure data type that any implementation can create - type TransactionData interface { - From() common.Address - To() *common.Address - Value() *big.Int - Data() []byte - } - ``` - -### Why Purity Matters - -Interface purity is crucial because it: -- Preserves implementation freedom -- Prevents accidental coupling to specific backends -- Enables creation of new implementations without constraints -- Allows mixing different implementation types (e.g., partial fakes) - -### Example: Maintaining Purity - -```go -// IMPURE: Forces dependency on eth client -type Chain interface { - GetEthClient() *ethclient.Client // 👎 Locks us to specific client -} - -// PURE: Allows any implementation -type Chain interface { - Client() (ChainClient, error) // 👍 Implementation-agnostic -} - -type ChainClient interface { - BlockNumber(ctx context.Context) (uint64, error) - // ... other methods -} -``` - -## Interface Hierarchy - -### System - -The top-level interface representing a complete Optimism deployment: - -```go -type System interface { - // Unique identifier for this system - Identifier() string - - // Access to L1 chain - L1() Chain - - // Access to L2 chain(s) - L2(chainID uint64) Chain -} -``` - -### Chain - -Represents an individual chain (L1 or L2) within the system: - -```go -type Chain interface { - // Chain identification - RPCURL() string - ID() types.ChainID - - // Core functionality - Client() (*ethclient.Client, error) - Wallets(ctx context.Context) ([]Wallet, error) - ContractsRegistry() interfaces.ContractsRegistry - - // Chain capabilities - SupportsEIP(ctx context.Context, eip uint64) bool - - // Transaction management - GasPrice(ctx context.Context) (*big.Int, error) - GasLimit(ctx context.Context, tx TransactionData) (uint64, error) - PendingNonceAt(ctx context.Context, address common.Address) (uint64, error) -} -``` - -### Wallet - -Manages accounts and transaction signing: - -```go -type Wallet interface { - // Account management - PrivateKey() types.Key - Address() types.Address - Balance() types.Balance - Nonce() uint64 - - // Transaction operations - Sign(tx Transaction) (Transaction, error) - Send(ctx context.Context, tx Transaction) error - - // Convenience methods - SendETH(to types.Address, amount types.Balance) types.WriteInvocation[any] - Transactor() *bind.TransactOpts -} -``` - -## Implementation Types - -The interfaces can be implemented in various ways to suit different needs: - -### 1. Real Deployments -- **Kurtosis-based**: Full containerized deployment -- **Netchef**: Remote devnet deployment -- -### 2. Testing Implementations -- **In-memory**: Fast, lightweight implementation for unit tests -- **Mocks**: Controlled behavior for specific test scenarios -- **Recording**: Record and replay real interactions - -### 3. Specialized Implementations -- **Partial**: Combining pieces from fake and real deployments -- **Filtered**: Limited functionality for specific use cases -- **Instrumented**: Added logging/metrics for debugging - -## Usage Examples - -### Writing Tests - -The System interfaces are primarily used through our testing framework. See the [Testing Framework](./testing.md) documentation for detailed examples and best practices. - -### Creating a Mock Implementation - -```go -type MockSystem struct { - l1 *MockChain - l2Map map[uint64]*MockChain -} - -func NewMockSystem() *MockSystem { - return &MockSystem{ - l1: NewMockChain(), - l2Map: make(map[uint64]*MockChain), - } -} - -// Implement System interface... -``` - -## Benefits - -- **Abstraction**: Automation code is isolated from deployment details -- **Flexibility**: Easy to add new deployment types -- **Testability**: Support for various testing approaches -- **Consistency**: Same interface across all implementations -- **Extensibility**: Can add specialized implementations for specific needs - -## Best Practices - -1. **Write Against Interfaces**: Never depend on specific implementations -2. **Use Context**: For proper cancellation and timeouts -3. **Handle Errors**: All operations can fail -4. **Test Multiple Implementations**: Ensure code works across different types -5. **Consider Performance**: Choose appropriate implementation for use case - -The System interfaces provide a powerful abstraction layer that enables writing robust, deployment-agnostic automation code while supporting a wide range of implementation types for different use cases. diff --git a/devnet-sdk/book/src/testing.md b/devnet-sdk/book/src/testing.md deleted file mode 100644 index c88a3cb28b4e6..0000000000000 --- a/devnet-sdk/book/src/testing.md +++ /dev/null @@ -1,165 +0,0 @@ -# Testing Framework - -The devnet-sdk provides a comprehensive testing framework designed to make testing against Optimism devnets both powerful and developer-friendly. - -## Testing Philosophy - -Our testing approach is built on several key principles: - -### 1. Native Go Tests - -Tests are written as standard Go tests, providing: -- Full IDE integration -- Native debugging capabilities -- Familiar testing patterns -- Integration with standard Go tooling - -```go -func TestSystemWrapETH(t *testing.T) { - // Standard Go test function - systest.SystemTest(t, wrapETHScenario(...)) -} -``` - -### 2. Safe Test Execution - -Tests are designed to be safe and self-aware: -- Tests verify their prerequisites before execution -- Tests skip gracefully when prerequisites aren't met -- Clear distinction between precondition failures and test failures - -```go -// Test will skip if the system doesn't support required features -walletGetter, fundsValidator := validators.AcquireL2WalletWithFunds( - chainIdx, - types.NewBalance(big.NewInt(1.0 * constants.ETH)), -) -``` - -### 3. Testable Scenarios - -Test scenarios themselves are designed to be testable: -- Scenarios work against any compliant System implementation -- Mocks and fakes can be used for scenario validation -- Clear separation between test logic and system interaction - -### 4. Framework Integration - -The `systest` package provides integration helpers that: -- Handle system acquisition and setup -- Manage test context and cleanup -- Provide precondition validation -- Enable consistent test patterns - -## Example Test - -Here's a complete example showing these principles in action: - -```go -import ( - "math/big" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" - "github.com/ethereum-optimism/optimism/devnet-sdk/system" - "github.com/ethereum-optimism/optimism/devnet-sdk/testing/systest" - "github.com/ethereum-optimism/optimism/devnet-sdk/testing/testlib/validators" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum/go-ethereum/log" - "github.com/stretchr/testify/require" -) - -// Define test scenario as a function that works with any System implementation -func wrapETHScenario(chainIdx uint64, walletGetter validators.WalletGetter) systest.SystemTestFunc { - return func(t systest.T, sys system.System) { - ctx := t.Context() - - logger := testlog.Logger(t, log.LevelInfo) - logger := logger.With("test", "WrapETH", "devnet", sys.Identifier()) - - // Get the L2 chain we want to test with - chain := sys.L2(chainIdx) - logger = logger.With("chain", chain.ID()) - - // Get a funded wallet for testing - user := walletGetter(ctx) - - // Access contract registry - wethAddr := constants.WETH - weth, err := chain.ContractsRegistry().WETH(wethAddr) - require.NoError(t, err) - - // Test logic using pure interfaces - funds := types.NewBalance(big.NewInt(0.5 * constants.ETH)) - initialBalance, err := weth.BalanceOf(user.Address()).Call(ctx) - require.NoError(t, err) - - require.NoError(t, user.SendETH(wethAddr, funds).Send(ctx).Wait()) - - finalBalance, err := weth.BalanceOf(user.Address()).Call(ctx) - require.NoError(t, err) - - require.Equal(t, initialBalance.Add(funds), finalBalance) - } -} - -func TestSystemWrapETH(t *testing.T) { - chainIdx := uint64(0) // First L2 chain - - // Setup wallet with required funds - this acts as a precondition - walletGetter, fundsValidator := validators.AcquireL2WalletWithFunds( - chainIdx, - types.NewBalance(big.NewInt(1.0 * constants.ETH)), - ) - - // Run the test with system management handled by framework - systest.SystemTest(t, - wrapETHScenario(chainIdx, walletGetter), - fundsValidator, - ) -} -``` - -## Framework Components - -### 1. Test Context Management - -The framework provides context management through `systest.T`: -- Proper test timeouts -- Cleanup handling -- Resource management -- Logging context - -### 2. Precondition Validators - -Validators ensure test prerequisites are met: -```go -// Validator ensures required funds are available -fundsValidator := validators.AcquireL2WalletWithFunds(...) -``` - -### 3. System Acquisition - -The framework handles system creation and setup: -```go -systest.SystemTest(t, func(t systest.T, sys system.System) { - // System is ready to use -}) -``` - -### 4. Resource Management - -Resources are properly managed: -- Automatic cleanup -- Proper error handling -- Context cancellation - -## Best Practices - -1. **Use Scenarios**: Write reusable test scenarios that work with any System implementation -2. **Validate Prerequisites**: Always check test prerequisites using validators -3. **Handle Resources**: Use the framework's resource management -4. **Use Pure Interfaces**: Write tests against the interfaces, not specific implementations -5. **Proper Logging**: Use structured logging with test context -6. **Clear Setup**: Keep test setup clear and explicit -7. **Error Handling**: Always handle errors and provide clear failure messages diff --git a/devnet-sdk/book/theme/css/footer.css b/devnet-sdk/book/theme/css/footer.css deleted file mode 100644 index cb7be80ab2145..0000000000000 --- a/devnet-sdk/book/theme/css/footer.css +++ /dev/null @@ -1,71 +0,0 @@ -.mdbook-footer { - width: 100%; - padding: 4rem 2.5rem; /* Increased padding */ - background-color: var(--bg); - border-top: 1px solid var(--sidebar-bg); - margin-top: 5rem; /* Increased margin */ -} - -.mdbook-footer .footer-container { - max-width: 1200px; - margin: 0 auto; - display: flex; - flex-direction: column; - gap: 2.5rem; /* Increased gap */ - align-items: center; -} - -.mdbook-footer .policy-links { - display: flex; - gap: 4rem; /* Increased gap between links */ - flex-wrap: wrap; - justify-content: center; -} - -.mdbook-footer .policy-links a { - color: var(--fg); - text-decoration: none; - transition: opacity 0.2s; - font-size: 1.35rem; /* Increased font size */ - opacity: 0.85; - font-weight: 400; - line-height: 1.6; /* Increased line height */ -} - -.mdbook-footer .policy-links a:hover { - opacity: 1; - text-decoration: underline; -} - -.mdbook-footer .copyright { - color: var(--fg); - font-size: 1.35rem; /* Increased font size */ - opacity: 0.85; - text-align: center; - font-weight: 400; - line-height: 1.6; /* Increased line height */ -} - -.mdbook-footer .copyright a { - color: var(--fg); - text-decoration: none; -} - -.mdbook-footer .copyright a:hover { - text-decoration: underline; -} - -@media (max-width: 640px) { - .mdbook-footer .policy-links { - gap: 2.5rem; /* Increased gap for mobile */ - } - - .mdbook-footer { - padding: 3rem 2rem; /* Increased padding for mobile */ - } - - .mdbook-footer .policy-links a, - .mdbook-footer .copyright { - font-size: 1.25rem; /* Increased font size for mobile */ - } -} \ No newline at end of file diff --git a/devnet-sdk/book/theme/js/footer.js b/devnet-sdk/book/theme/js/footer.js deleted file mode 100644 index 014f44f2d6c54..0000000000000 --- a/devnet-sdk/book/theme/js/footer.js +++ /dev/null @@ -1,41 +0,0 @@ -// Create footer element -function createFooter() { - const footer = document.createElement('footer'); - footer.className = 'mdbook-footer'; - - const container = document.createElement('div'); - container.className = 'footer-container'; - - // Add legal links - const policyLinks = document.createElement('div'); - policyLinks.className = 'policy-links'; - - const links = [ - { href: 'https://optimism.io/community-agreement', text: 'Community Agreement' }, - { href: 'https://optimism.io/terms', text: 'Terms of Service' }, - { href: 'https://optimism.io/data-privacy-policy', text: 'Privacy Policy' } - ]; - - links.forEach(link => { - const a = document.createElement('a'); - a.href = link.href; - a.textContent = link.text; - policyLinks.appendChild(a); - }); - - // Add copyright notice - const copyright = document.createElement('div'); - copyright.className = 'copyright'; - copyright.innerHTML = `© ${new Date().getFullYear()} Optimism Foundation. All rights reserved.`; - - // Assemble footer - container.appendChild(policyLinks); - container.appendChild(copyright); - footer.appendChild(container); - - // Add footer to page - document.body.appendChild(footer); -} - -// Run after DOM is loaded -document.addEventListener('DOMContentLoaded', createFooter); \ No newline at end of file diff --git a/devnet-sdk/cmd/mf2kt/main.go b/devnet-sdk/cmd/mf2kt/main.go deleted file mode 100644 index de1ad36b4ec79..0000000000000 --- a/devnet-sdk/cmd/mf2kt/main.go +++ /dev/null @@ -1,72 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/ethereum-optimism/optimism/devnet-sdk/kt" - "github.com/ethereum-optimism/optimism/devnet-sdk/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/devnet-sdk/constraints/constraints.go b/devnet-sdk/constraints/constraints.go deleted file mode 100644 index c4566bb350408..0000000000000 --- a/devnet-sdk/constraints/constraints.go +++ /dev/null @@ -1,26 +0,0 @@ -package constraints - -import ( - "github.com/ethereum/go-ethereum/log" - - "github.com/ethereum-optimism/optimism/devnet-sdk/system" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" -) - -type WalletConstraint interface { - CheckWallet(wallet system.Wallet) bool -} - -type WalletConstraintFunc func(wallet system.Wallet) bool - -func (f WalletConstraintFunc) CheckWallet(wallet system.Wallet) bool { - return f(wallet) -} - -func WithBalance(amount types.Balance) WalletConstraint { - return WalletConstraintFunc(func(wallet system.Wallet) bool { - balance := wallet.Balance() - log.Debug("checking balance", "wallet", wallet.Address(), "balance", balance, "needed", amount) - return balance.GreaterThan(amount) - }) -} diff --git a/devnet-sdk/constraints/constraints_test.go b/devnet-sdk/constraints/constraints_test.go deleted file mode 100644 index b55bd2892d261..0000000000000 --- a/devnet-sdk/constraints/constraints_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package constraints - -import ( - "context" - "math/big" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/system" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/stretchr/testify/assert" -) - -type mockWallet struct { - balance types.Balance - address types.Address -} - -func (m mockWallet) Balance() types.Balance { - return m.balance -} - -func (m mockWallet) Address() types.Address { - return m.address -} - -func (m mockWallet) PrivateKey() types.Key { - key, _ := crypto.HexToECDSA("123") - return types.Key(key) -} - -func (m mockWallet) SendETH(to types.Address, amount types.Balance) types.WriteInvocation[any] { - panic("not implemented") -} - -func (m mockWallet) InitiateMessage(chainID types.ChainID, target common.Address, message []byte) types.WriteInvocation[any] { - panic("not implemented") -} - -func (m mockWallet) ExecuteMessage(identifier bindings.Identifier, sentMessage []byte) types.WriteInvocation[any] { - panic("not implemented") -} - -func (m mockWallet) Nonce() uint64 { - return 0 -} - -func (m mockWallet) Sign(tx system.Transaction) (system.Transaction, error) { - return tx, nil -} - -func (m mockWallet) Send(ctx context.Context, tx system.Transaction) error { - return nil -} - -func (m mockWallet) Transactor() *bind.TransactOpts { - return nil -} - -var _ system.Wallet = (*mockWallet)(nil) - -func newBigInt(x int64) *big.Int { - return big.NewInt(x) -} - -func TestWithBalance(t *testing.T) { - tests := []struct { - name string - walletBalance types.Balance - requiredAmount types.Balance - expectPass bool - }{ - { - name: "balance greater than required", - walletBalance: types.NewBalance(newBigInt(200)), - requiredAmount: types.NewBalance(newBigInt(100)), - expectPass: true, - }, - { - name: "balance equal to required", - walletBalance: types.NewBalance(newBigInt(100)), - requiredAmount: types.NewBalance(newBigInt(100)), - expectPass: false, - }, - { - name: "balance less than required", - walletBalance: types.NewBalance(newBigInt(50)), - requiredAmount: types.NewBalance(newBigInt(100)), - expectPass: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - wallet := mockWallet{ - balance: tt.walletBalance, - address: common.HexToAddress("0x123"), - } - constraint := WithBalance(tt.requiredAmount) - result := constraint.CheckWallet(wallet) - assert.Equal(t, tt.expectPass, result, "balance check should match expected result") - }) - } -} - -func TestWalletConstraintFunc(t *testing.T) { - called := false - testFunc := WalletConstraintFunc(func(wallet system.Wallet) bool { - called = true - return true - }) - - wallet := mockWallet{ - balance: types.NewBalance(newBigInt(100)), - address: common.HexToAddress("0x123"), - } - - result := testFunc.CheckWallet(wallet) - assert.True(t, called, "constraint function should have been called") - assert.True(t, result, "constraint function should return true") -} diff --git a/devnet-sdk/contracts/bindings/eventlogger.go b/devnet-sdk/contracts/bindings/eventlogger.go deleted file mode 100644 index c92749341e9cb..0000000000000 --- a/devnet-sdk/contracts/bindings/eventlogger.go +++ /dev/null @@ -1,68 +0,0 @@ -package bindings - -// This file was generated and edited by below sequences: -// cd packages/contracts-bedrock -// solc --optimize --bin --abi -o out src/integration/EventLogger.sol -// abigen --abi out/EventLogger.abi --bin out/EventLogger.bin --pkg bindings --out eventlogger.go -// Resulting eventlogger.go was moved to this file, and only the needed implementation was left here. - -import ( - "errors" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" -) - -// EventloggerMetaData contains all meta data concerning the Eventlogger contract. -var EventloggerMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"_topics\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"emitLog\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"origin\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"logIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"structIdentifier\",\"name\":\"_id\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_msgHash\",\"type\":\"bytes32\"}],\"name\":\"validateMessage\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x6080604052348015600e575f80fd5b506102ac8061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c8063ab4d6f7514610038578063edebc13b1461004d575b5f80fd5b61004b61004636600461013e565b610060565b005b61004b61005b36600461016c565b6100bd565b60405163ab4d6f7560e01b81526022602160991b019063ab4d6f759061008c9085908590600401610226565b5f604051808303815f87803b1580156100a3575f80fd5b505af11580156100b5573d5f803e3d5ffd5b505050505050565b80604051818482378486356020880135604089013560608a0135848015610102576001811461010a5760028114610113576003811461011d5760048114610128575f80fd5b8787a0610130565b848888a1610130565b83858989a2610130565b8284868a8aa3610130565b818385878b8ba45b505050505050505050505050565b5f8082840360c0811215610150575f80fd5b60a081121561015d575f80fd5b50919360a08501359350915050565b5f805f806040858703121561017f575f80fd5b843567ffffffffffffffff80821115610196575f80fd5b818701915087601f8301126101a9575f80fd5b8135818111156101b7575f80fd5b8860208260051b85010111156101cb575f80fd5b6020928301965094509086013590808211156101e5575f80fd5b818701915087601f8301126101f8575f80fd5b813581811115610206575f80fd5b886020828501011115610217575f80fd5b95989497505060200194505050565b60c0810183356001600160a01b038116808214610241575f80fd5b8352506020848101359083015260408085013590830152606080850135908301526080938401359382019390935260a001529056fea26469706673582212206da9bc84d514e1a78e2b4160f99f93aa58672040ece82f45ac2a878aeefdfbe164736f6c63430008190033", -} - -// EventloggerABI is the input ABI used to generate the binding from. -// Deprecated: Use EventloggerMetaData.ABI instead. -var EventloggerABI = EventloggerMetaData.ABI - -// EventloggerBin is the compiled bytecode used for deploying new contracts. -// Deprecated: Use EventloggerMetaData.Bin instead. -var EventloggerBin = EventloggerMetaData.Bin - -// DeployEventlogger deploys a new Ethereum contract, binding an instance of Eventlogger to it. -func DeployEventlogger(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Eventlogger, error) { - parsed, err := EventloggerMetaData.GetAbi() - if err != nil { - return common.Address{}, nil, nil, err - } - if parsed == nil { - return common.Address{}, nil, nil, errors.New("GetABI returned nil") - } - - address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(EventloggerBin), backend) - if err != nil { - return common.Address{}, nil, nil, err - } - return address, tx, &Eventlogger{EventloggerCaller: EventloggerCaller{contract: contract}, EventloggerTransactor: EventloggerTransactor{contract: contract}, EventloggerFilterer: EventloggerFilterer{contract: contract}}, nil -} - -// Eventlogger is an auto generated Go binding around an Ethereum contract. -type Eventlogger struct { - EventloggerCaller // Read-only binding to the contract - EventloggerTransactor // Write-only binding to the contract - EventloggerFilterer // Log filterer for contract events -} - -// EventloggerCaller is an auto generated read-only Go binding around an Ethereum contract. -type EventloggerCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// EventloggerTransactor is an auto generated write-only Go binding around an Ethereum contract. -type EventloggerTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// EventloggerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type EventloggerFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} diff --git a/devnet-sdk/contracts/bindings/l2tol2crossdomainmessenger.go b/devnet-sdk/contracts/bindings/l2tol2crossdomainmessenger.go deleted file mode 100644 index 01d733398b636..0000000000000 --- a/devnet-sdk/contracts/bindings/l2tol2crossdomainmessenger.go +++ /dev/null @@ -1,788 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package bindings - -import ( - "errors" - "math/big" - "strings" - - ethereum "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = errors.New - _ = big.NewInt - _ = strings.NewReader - _ = ethereum.NotFound - _ = bind.Bind - _ = common.Big1 - _ = types.BloomLookup - _ = event.NewSubscription -) - -// Identifier is an auto generated low-level Go binding around an user-defined struct. -type Identifier struct { - Origin common.Address - BlockNumber *big.Int - LogIndex *big.Int - Timestamp *big.Int - ChainId *big.Int -} - -// L2ToL2CrossDomainMessengerMetaData contains all meta data concerning the L2ToL2CrossDomainMessenger contract. -var L2ToL2CrossDomainMessengerMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"name\":\"crossDomainMessageContext\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"sender_\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"source_\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"crossDomainMessageSender\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"sender_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"crossDomainMessageSource\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"source_\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"messageNonce\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"messageVersion\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"origin\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"logIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"structIdentifier\",\"name\":\"_id\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"_sentMessage\",\"type\":\"bytes\"}],\"name\":\"relayMessage\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"returnData_\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_destination\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_message\",\"type\":\"bytes\"}],\"name\":\"sendMessage\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"successfulMessages\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"source\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"messageNonce\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"messageHash\",\"type\":\"bytes32\"}],\"name\":\"RelayedMessage\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"destination\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"messageNonce\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"}],\"name\":\"SentMessage\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"EventPayloadNotSentMessage\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"IdOriginNotL2ToL2CrossDomainMessenger\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidChainId\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MessageAlreadyRelayed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MessageDestinationNotRelayChain\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MessageDestinationSameChain\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MessageTargetCrossL2Inbox\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MessageTargetL2ToL2CrossDomainMessenger\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotEntered\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ReentrantCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TargetCallFailed\",\"type\":\"error\"}]", -} - -// L2ToL2CrossDomainMessengerABI is the input ABI used to generate the binding from. -// Deprecated: Use L2ToL2CrossDomainMessengerMetaData.ABI instead. -var L2ToL2CrossDomainMessengerABI = L2ToL2CrossDomainMessengerMetaData.ABI - -// L2ToL2CrossDomainMessenger is an auto generated Go binding around an Ethereum contract. -type L2ToL2CrossDomainMessenger struct { - L2ToL2CrossDomainMessengerCaller // Read-only binding to the contract - L2ToL2CrossDomainMessengerTransactor // Write-only binding to the contract - L2ToL2CrossDomainMessengerFilterer // Log filterer for contract events -} - -// L2ToL2CrossDomainMessengerCaller is an auto generated read-only Go binding around an Ethereum contract. -type L2ToL2CrossDomainMessengerCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// L2ToL2CrossDomainMessengerTransactor is an auto generated write-only Go binding around an Ethereum contract. -type L2ToL2CrossDomainMessengerTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// L2ToL2CrossDomainMessengerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type L2ToL2CrossDomainMessengerFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// L2ToL2CrossDomainMessengerSession is an auto generated Go binding around an Ethereum contract, -// with pre-set call and transact options. -type L2ToL2CrossDomainMessengerSession struct { - Contract *L2ToL2CrossDomainMessenger // Generic contract binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// L2ToL2CrossDomainMessengerCallerSession is an auto generated read-only Go binding around an Ethereum contract, -// with pre-set call options. -type L2ToL2CrossDomainMessengerCallerSession struct { - Contract *L2ToL2CrossDomainMessengerCaller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session -} - -// L2ToL2CrossDomainMessengerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, -// with pre-set transact options. -type L2ToL2CrossDomainMessengerTransactorSession struct { - Contract *L2ToL2CrossDomainMessengerTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// L2ToL2CrossDomainMessengerRaw is an auto generated low-level Go binding around an Ethereum contract. -type L2ToL2CrossDomainMessengerRaw struct { - Contract *L2ToL2CrossDomainMessenger // Generic contract binding to access the raw methods on -} - -// L2ToL2CrossDomainMessengerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. -type L2ToL2CrossDomainMessengerCallerRaw struct { - Contract *L2ToL2CrossDomainMessengerCaller // Generic read-only contract binding to access the raw methods on -} - -// L2ToL2CrossDomainMessengerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. -type L2ToL2CrossDomainMessengerTransactorRaw struct { - Contract *L2ToL2CrossDomainMessengerTransactor // Generic write-only contract binding to access the raw methods on -} - -// NewL2ToL2CrossDomainMessenger creates a new instance of L2ToL2CrossDomainMessenger, bound to a specific deployed contract. -func NewL2ToL2CrossDomainMessenger(address common.Address, backend bind.ContractBackend) (*L2ToL2CrossDomainMessenger, error) { - contract, err := bindL2ToL2CrossDomainMessenger(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &L2ToL2CrossDomainMessenger{L2ToL2CrossDomainMessengerCaller: L2ToL2CrossDomainMessengerCaller{contract: contract}, L2ToL2CrossDomainMessengerTransactor: L2ToL2CrossDomainMessengerTransactor{contract: contract}, L2ToL2CrossDomainMessengerFilterer: L2ToL2CrossDomainMessengerFilterer{contract: contract}}, nil -} - -// NewL2ToL2CrossDomainMessengerCaller creates a new read-only instance of L2ToL2CrossDomainMessenger, bound to a specific deployed contract. -func NewL2ToL2CrossDomainMessengerCaller(address common.Address, caller bind.ContractCaller) (*L2ToL2CrossDomainMessengerCaller, error) { - contract, err := bindL2ToL2CrossDomainMessenger(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &L2ToL2CrossDomainMessengerCaller{contract: contract}, nil -} - -// NewL2ToL2CrossDomainMessengerTransactor creates a new write-only instance of L2ToL2CrossDomainMessenger, bound to a specific deployed contract. -func NewL2ToL2CrossDomainMessengerTransactor(address common.Address, transactor bind.ContractTransactor) (*L2ToL2CrossDomainMessengerTransactor, error) { - contract, err := bindL2ToL2CrossDomainMessenger(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &L2ToL2CrossDomainMessengerTransactor{contract: contract}, nil -} - -// NewL2ToL2CrossDomainMessengerFilterer creates a new log filterer instance of L2ToL2CrossDomainMessenger, bound to a specific deployed contract. -func NewL2ToL2CrossDomainMessengerFilterer(address common.Address, filterer bind.ContractFilterer) (*L2ToL2CrossDomainMessengerFilterer, error) { - contract, err := bindL2ToL2CrossDomainMessenger(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &L2ToL2CrossDomainMessengerFilterer{contract: contract}, nil -} - -// bindL2ToL2CrossDomainMessenger binds a generic wrapper to an already deployed contract. -func bindL2ToL2CrossDomainMessenger(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := abi.JSON(strings.NewReader(L2ToL2CrossDomainMessengerABI)) - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _L2ToL2CrossDomainMessenger.Contract.L2ToL2CrossDomainMessengerCaller.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.L2ToL2CrossDomainMessengerTransactor.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.L2ToL2CrossDomainMessengerTransactor.contract.Transact(opts, method, params...) -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _L2ToL2CrossDomainMessenger.Contract.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.contract.Transact(opts, method, params...) -} - -// CrossDomainMessageContext is a free data retrieval call binding the contract method 0x7936cbee. -// -// Solidity: function crossDomainMessageContext() view returns(address sender_, uint256 source_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCaller) CrossDomainMessageContext(opts *bind.CallOpts) (struct { - Sender common.Address - Source *big.Int -}, error) { - var out []interface{} - err := _L2ToL2CrossDomainMessenger.contract.Call(opts, &out, "crossDomainMessageContext") - - outstruct := new(struct { - Sender common.Address - Source *big.Int - }) - if err != nil { - return *outstruct, err - } - - outstruct.Sender = *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - outstruct.Source = *abi.ConvertType(out[1], new(*big.Int)).(**big.Int) - - return *outstruct, err - -} - -// CrossDomainMessageContext is a free data retrieval call binding the contract method 0x7936cbee. -// -// Solidity: function crossDomainMessageContext() view returns(address sender_, uint256 source_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) CrossDomainMessageContext() (struct { - Sender common.Address - Source *big.Int -}, error) { - return _L2ToL2CrossDomainMessenger.Contract.CrossDomainMessageContext(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// CrossDomainMessageContext is a free data retrieval call binding the contract method 0x7936cbee. -// -// Solidity: function crossDomainMessageContext() view returns(address sender_, uint256 source_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerSession) CrossDomainMessageContext() (struct { - Sender common.Address - Source *big.Int -}, error) { - return _L2ToL2CrossDomainMessenger.Contract.CrossDomainMessageContext(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// CrossDomainMessageSender is a free data retrieval call binding the contract method 0x38ffde18. -// -// Solidity: function crossDomainMessageSender() view returns(address sender_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCaller) CrossDomainMessageSender(opts *bind.CallOpts) (common.Address, error) { - var out []interface{} - err := _L2ToL2CrossDomainMessenger.contract.Call(opts, &out, "crossDomainMessageSender") - - if err != nil { - return *new(common.Address), err - } - - out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - - return out0, err - -} - -// CrossDomainMessageSender is a free data retrieval call binding the contract method 0x38ffde18. -// -// Solidity: function crossDomainMessageSender() view returns(address sender_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) CrossDomainMessageSender() (common.Address, error) { - return _L2ToL2CrossDomainMessenger.Contract.CrossDomainMessageSender(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// CrossDomainMessageSender is a free data retrieval call binding the contract method 0x38ffde18. -// -// Solidity: function crossDomainMessageSender() view returns(address sender_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerSession) CrossDomainMessageSender() (common.Address, error) { - return _L2ToL2CrossDomainMessenger.Contract.CrossDomainMessageSender(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// CrossDomainMessageSource is a free data retrieval call binding the contract method 0x24794462. -// -// Solidity: function crossDomainMessageSource() view returns(uint256 source_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCaller) CrossDomainMessageSource(opts *bind.CallOpts) (*big.Int, error) { - var out []interface{} - err := _L2ToL2CrossDomainMessenger.contract.Call(opts, &out, "crossDomainMessageSource") - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// CrossDomainMessageSource is a free data retrieval call binding the contract method 0x24794462. -// -// Solidity: function crossDomainMessageSource() view returns(uint256 source_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) CrossDomainMessageSource() (*big.Int, error) { - return _L2ToL2CrossDomainMessenger.Contract.CrossDomainMessageSource(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// CrossDomainMessageSource is a free data retrieval call binding the contract method 0x24794462. -// -// Solidity: function crossDomainMessageSource() view returns(uint256 source_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerSession) CrossDomainMessageSource() (*big.Int, error) { - return _L2ToL2CrossDomainMessenger.Contract.CrossDomainMessageSource(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// MessageNonce is a free data retrieval call binding the contract method 0xecc70428. -// -// Solidity: function messageNonce() view returns(uint256) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCaller) MessageNonce(opts *bind.CallOpts) (*big.Int, error) { - var out []interface{} - err := _L2ToL2CrossDomainMessenger.contract.Call(opts, &out, "messageNonce") - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// MessageNonce is a free data retrieval call binding the contract method 0xecc70428. -// -// Solidity: function messageNonce() view returns(uint256) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) MessageNonce() (*big.Int, error) { - return _L2ToL2CrossDomainMessenger.Contract.MessageNonce(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// MessageNonce is a free data retrieval call binding the contract method 0xecc70428. -// -// Solidity: function messageNonce() view returns(uint256) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerSession) MessageNonce() (*big.Int, error) { - return _L2ToL2CrossDomainMessenger.Contract.MessageNonce(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// MessageVersion is a free data retrieval call binding the contract method 0x52617f3c. -// -// Solidity: function messageVersion() view returns(uint16) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCaller) MessageVersion(opts *bind.CallOpts) (uint16, error) { - var out []interface{} - err := _L2ToL2CrossDomainMessenger.contract.Call(opts, &out, "messageVersion") - - if err != nil { - return *new(uint16), err - } - - out0 := *abi.ConvertType(out[0], new(uint16)).(*uint16) - - return out0, err - -} - -// MessageVersion is a free data retrieval call binding the contract method 0x52617f3c. -// -// Solidity: function messageVersion() view returns(uint16) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) MessageVersion() (uint16, error) { - return _L2ToL2CrossDomainMessenger.Contract.MessageVersion(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// MessageVersion is a free data retrieval call binding the contract method 0x52617f3c. -// -// Solidity: function messageVersion() view returns(uint16) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerSession) MessageVersion() (uint16, error) { - return _L2ToL2CrossDomainMessenger.Contract.MessageVersion(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// SuccessfulMessages is a free data retrieval call binding the contract method 0xb1b1b209. -// -// Solidity: function successfulMessages(bytes32 ) view returns(bool) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCaller) SuccessfulMessages(opts *bind.CallOpts, arg0 [32]byte) (bool, error) { - var out []interface{} - err := _L2ToL2CrossDomainMessenger.contract.Call(opts, &out, "successfulMessages", arg0) - - if err != nil { - return *new(bool), err - } - - out0 := *abi.ConvertType(out[0], new(bool)).(*bool) - - return out0, err - -} - -// SuccessfulMessages is a free data retrieval call binding the contract method 0xb1b1b209. -// -// Solidity: function successfulMessages(bytes32 ) view returns(bool) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) SuccessfulMessages(arg0 [32]byte) (bool, error) { - return _L2ToL2CrossDomainMessenger.Contract.SuccessfulMessages(&_L2ToL2CrossDomainMessenger.CallOpts, arg0) -} - -// SuccessfulMessages is a free data retrieval call binding the contract method 0xb1b1b209. -// -// Solidity: function successfulMessages(bytes32 ) view returns(bool) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerSession) SuccessfulMessages(arg0 [32]byte) (bool, error) { - return _L2ToL2CrossDomainMessenger.Contract.SuccessfulMessages(&_L2ToL2CrossDomainMessenger.CallOpts, arg0) -} - -// Version is a free data retrieval call binding the contract method 0x54fd4d50. -// -// Solidity: function version() view returns(string) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCaller) Version(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _L2ToL2CrossDomainMessenger.contract.Call(opts, &out, "version") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// Version is a free data retrieval call binding the contract method 0x54fd4d50. -// -// Solidity: function version() view returns(string) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) Version() (string, error) { - return _L2ToL2CrossDomainMessenger.Contract.Version(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// Version is a free data retrieval call binding the contract method 0x54fd4d50. -// -// Solidity: function version() view returns(string) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerSession) Version() (string, error) { - return _L2ToL2CrossDomainMessenger.Contract.Version(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// RelayMessage is a paid mutator transaction binding the contract method 0x8d1d298f. -// -// Solidity: function relayMessage((address,uint256,uint256,uint256,uint256) _id, bytes _sentMessage) payable returns(bytes returnData_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerTransactor) RelayMessage(opts *bind.TransactOpts, _id Identifier, _sentMessage []byte) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.contract.Transact(opts, "relayMessage", _id, _sentMessage) -} - -// RelayMessage is a paid mutator transaction binding the contract method 0x8d1d298f. -// -// Solidity: function relayMessage((address,uint256,uint256,uint256,uint256) _id, bytes _sentMessage) payable returns(bytes returnData_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) RelayMessage(_id Identifier, _sentMessage []byte) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.RelayMessage(&_L2ToL2CrossDomainMessenger.TransactOpts, _id, _sentMessage) -} - -// RelayMessage is a paid mutator transaction binding the contract method 0x8d1d298f. -// -// Solidity: function relayMessage((address,uint256,uint256,uint256,uint256) _id, bytes _sentMessage) payable returns(bytes returnData_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerTransactorSession) RelayMessage(_id Identifier, _sentMessage []byte) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.RelayMessage(&_L2ToL2CrossDomainMessenger.TransactOpts, _id, _sentMessage) -} - -// SendMessage is a paid mutator transaction binding the contract method 0x7056f41f. -// -// Solidity: function sendMessage(uint256 _destination, address _target, bytes _message) returns(bytes32) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerTransactor) SendMessage(opts *bind.TransactOpts, _destination *big.Int, _target common.Address, _message []byte) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.contract.Transact(opts, "sendMessage", _destination, _target, _message) -} - -// SendMessage is a paid mutator transaction binding the contract method 0x7056f41f. -// -// Solidity: function sendMessage(uint256 _destination, address _target, bytes _message) returns(bytes32) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) SendMessage(_destination *big.Int, _target common.Address, _message []byte) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.SendMessage(&_L2ToL2CrossDomainMessenger.TransactOpts, _destination, _target, _message) -} - -// SendMessage is a paid mutator transaction binding the contract method 0x7056f41f. -// -// Solidity: function sendMessage(uint256 _destination, address _target, bytes _message) returns(bytes32) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerTransactorSession) SendMessage(_destination *big.Int, _target common.Address, _message []byte) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.SendMessage(&_L2ToL2CrossDomainMessenger.TransactOpts, _destination, _target, _message) -} - -// L2ToL2CrossDomainMessengerRelayedMessageIterator is returned from FilterRelayedMessage and is used to iterate over the raw logs and unpacked data for RelayedMessage events raised by the L2ToL2CrossDomainMessenger contract. -type L2ToL2CrossDomainMessengerRelayedMessageIterator struct { - Event *L2ToL2CrossDomainMessengerRelayedMessage // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *L2ToL2CrossDomainMessengerRelayedMessageIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(L2ToL2CrossDomainMessengerRelayedMessage) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(L2ToL2CrossDomainMessengerRelayedMessage) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *L2ToL2CrossDomainMessengerRelayedMessageIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *L2ToL2CrossDomainMessengerRelayedMessageIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// L2ToL2CrossDomainMessengerRelayedMessage represents a RelayedMessage event raised by the L2ToL2CrossDomainMessenger contract. -type L2ToL2CrossDomainMessengerRelayedMessage struct { - Source *big.Int - MessageNonce *big.Int - MessageHash [32]byte - Raw types.Log // Blockchain specific contextual infos -} - -// FilterRelayedMessage is a free log retrieval operation binding the contract event 0x5948076590932b9d173029c7df03fe386e755a61c86c7fe2671011a2faa2a379. -// -// Solidity: event RelayedMessage(uint256 indexed source, uint256 indexed messageNonce, bytes32 indexed messageHash) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerFilterer) FilterRelayedMessage(opts *bind.FilterOpts, source []*big.Int, messageNonce []*big.Int, messageHash [][32]byte) (*L2ToL2CrossDomainMessengerRelayedMessageIterator, error) { - - var sourceRule []interface{} - for _, sourceItem := range source { - sourceRule = append(sourceRule, sourceItem) - } - var messageNonceRule []interface{} - for _, messageNonceItem := range messageNonce { - messageNonceRule = append(messageNonceRule, messageNonceItem) - } - var messageHashRule []interface{} - for _, messageHashItem := range messageHash { - messageHashRule = append(messageHashRule, messageHashItem) - } - - logs, sub, err := _L2ToL2CrossDomainMessenger.contract.FilterLogs(opts, "RelayedMessage", sourceRule, messageNonceRule, messageHashRule) - if err != nil { - return nil, err - } - return &L2ToL2CrossDomainMessengerRelayedMessageIterator{contract: _L2ToL2CrossDomainMessenger.contract, event: "RelayedMessage", logs: logs, sub: sub}, nil -} - -// WatchRelayedMessage is a free log subscription operation binding the contract event 0x5948076590932b9d173029c7df03fe386e755a61c86c7fe2671011a2faa2a379. -// -// Solidity: event RelayedMessage(uint256 indexed source, uint256 indexed messageNonce, bytes32 indexed messageHash) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerFilterer) WatchRelayedMessage(opts *bind.WatchOpts, sink chan<- *L2ToL2CrossDomainMessengerRelayedMessage, source []*big.Int, messageNonce []*big.Int, messageHash [][32]byte) (event.Subscription, error) { - - var sourceRule []interface{} - for _, sourceItem := range source { - sourceRule = append(sourceRule, sourceItem) - } - var messageNonceRule []interface{} - for _, messageNonceItem := range messageNonce { - messageNonceRule = append(messageNonceRule, messageNonceItem) - } - var messageHashRule []interface{} - for _, messageHashItem := range messageHash { - messageHashRule = append(messageHashRule, messageHashItem) - } - - logs, sub, err := _L2ToL2CrossDomainMessenger.contract.WatchLogs(opts, "RelayedMessage", sourceRule, messageNonceRule, messageHashRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(L2ToL2CrossDomainMessengerRelayedMessage) - if err := _L2ToL2CrossDomainMessenger.contract.UnpackLog(event, "RelayedMessage", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseRelayedMessage is a log parse operation binding the contract event 0x5948076590932b9d173029c7df03fe386e755a61c86c7fe2671011a2faa2a379. -// -// Solidity: event RelayedMessage(uint256 indexed source, uint256 indexed messageNonce, bytes32 indexed messageHash) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerFilterer) ParseRelayedMessage(log types.Log) (*L2ToL2CrossDomainMessengerRelayedMessage, error) { - event := new(L2ToL2CrossDomainMessengerRelayedMessage) - if err := _L2ToL2CrossDomainMessenger.contract.UnpackLog(event, "RelayedMessage", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// L2ToL2CrossDomainMessengerSentMessageIterator is returned from FilterSentMessage and is used to iterate over the raw logs and unpacked data for SentMessage events raised by the L2ToL2CrossDomainMessenger contract. -type L2ToL2CrossDomainMessengerSentMessageIterator struct { - Event *L2ToL2CrossDomainMessengerSentMessage // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *L2ToL2CrossDomainMessengerSentMessageIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(L2ToL2CrossDomainMessengerSentMessage) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(L2ToL2CrossDomainMessengerSentMessage) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *L2ToL2CrossDomainMessengerSentMessageIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *L2ToL2CrossDomainMessengerSentMessageIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// L2ToL2CrossDomainMessengerSentMessage represents a SentMessage event raised by the L2ToL2CrossDomainMessenger contract. -type L2ToL2CrossDomainMessengerSentMessage struct { - Destination *big.Int - Target common.Address - MessageNonce *big.Int - Sender common.Address - Message []byte - Raw types.Log // Blockchain specific contextual infos -} - -// FilterSentMessage is a free log retrieval operation binding the contract event 0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320. -// -// Solidity: event SentMessage(uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerFilterer) FilterSentMessage(opts *bind.FilterOpts, destination []*big.Int, target []common.Address, messageNonce []*big.Int) (*L2ToL2CrossDomainMessengerSentMessageIterator, error) { - - var destinationRule []interface{} - for _, destinationItem := range destination { - destinationRule = append(destinationRule, destinationItem) - } - var targetRule []interface{} - for _, targetItem := range target { - targetRule = append(targetRule, targetItem) - } - var messageNonceRule []interface{} - for _, messageNonceItem := range messageNonce { - messageNonceRule = append(messageNonceRule, messageNonceItem) - } - - logs, sub, err := _L2ToL2CrossDomainMessenger.contract.FilterLogs(opts, "SentMessage", destinationRule, targetRule, messageNonceRule) - if err != nil { - return nil, err - } - return &L2ToL2CrossDomainMessengerSentMessageIterator{contract: _L2ToL2CrossDomainMessenger.contract, event: "SentMessage", logs: logs, sub: sub}, nil -} - -// WatchSentMessage is a free log subscription operation binding the contract event 0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320. -// -// Solidity: event SentMessage(uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerFilterer) WatchSentMessage(opts *bind.WatchOpts, sink chan<- *L2ToL2CrossDomainMessengerSentMessage, destination []*big.Int, target []common.Address, messageNonce []*big.Int) (event.Subscription, error) { - - var destinationRule []interface{} - for _, destinationItem := range destination { - destinationRule = append(destinationRule, destinationItem) - } - var targetRule []interface{} - for _, targetItem := range target { - targetRule = append(targetRule, targetItem) - } - var messageNonceRule []interface{} - for _, messageNonceItem := range messageNonce { - messageNonceRule = append(messageNonceRule, messageNonceItem) - } - - logs, sub, err := _L2ToL2CrossDomainMessenger.contract.WatchLogs(opts, "SentMessage", destinationRule, targetRule, messageNonceRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(L2ToL2CrossDomainMessengerSentMessage) - if err := _L2ToL2CrossDomainMessenger.contract.UnpackLog(event, "SentMessage", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseSentMessage is a log parse operation binding the contract event 0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320. -// -// Solidity: event SentMessage(uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerFilterer) ParseSentMessage(log types.Log) (*L2ToL2CrossDomainMessengerSentMessage, error) { - event := new(L2ToL2CrossDomainMessengerSentMessage) - if err := _L2ToL2CrossDomainMessenger.contract.UnpackLog(event, "SentMessage", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} diff --git a/devnet-sdk/contracts/bindings/superchainweth.go b/devnet-sdk/contracts/bindings/superchainweth.go deleted file mode 100644 index e0049ff999669..0000000000000 --- a/devnet-sdk/contracts/bindings/superchainweth.go +++ /dev/null @@ -1,1879 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package bindings - -import ( - "errors" - "math/big" - "strings" - - ethereum "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = errors.New - _ = big.NewInt - _ = strings.NewReader - _ = ethereum.NotFound - _ = bind.Bind - _ = common.Big1 - _ = types.BloomLookup - _ = event.NewSubscription -) - -// SuperchainWETHMetaData contains all meta data concerning the SuperchainWETH contract. -var SuperchainWETHMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"fallback\",\"stateMutability\":\"payable\"},{\"type\":\"receive\",\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"allowance\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"approve\",\"inputs\":[{\"name\":\"guy\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"balanceOf\",\"inputs\":[{\"name\":\"src\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"crosschainBurn\",\"inputs\":[{\"name\":\"_from\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"crosschainMint\",\"inputs\":[{\"name\":\"_to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"decimals\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"deposit\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"name\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"relayETH\",\"inputs\":[{\"name\":\"_from\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"sendETH\",\"inputs\":[{\"name\":\"_to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_chainId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"msgHash_\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"supportsInterface\",\"inputs\":[{\"name\":\"_interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"symbol\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"totalSupply\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"transfer\",\"inputs\":[{\"name\":\"dst\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferFrom\",\"inputs\":[{\"name\":\"src\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"dst\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"version\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"withdraw\",\"inputs\":[{\"name\":\"_amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"Approval\",\"inputs\":[{\"name\":\"src\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"guy\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"CrosschainBurn\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"CrosschainMint\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Deposit\",\"inputs\":[{\"name\":\"dst\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RelayETH\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"source\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"SendETH\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"destination\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Transfer\",\"inputs\":[{\"name\":\"src\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"dst\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Withdrawal\",\"inputs\":[{\"name\":\"src\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"InvalidCrossDomainSender\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotCustomGasToken\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"Unauthorized\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ZeroAddress\",\"inputs\":[]}]", -} - -// SuperchainWETHABI is the input ABI used to generate the binding from. -// Deprecated: Use SuperchainWETHMetaData.ABI instead. -var SuperchainWETHABI = SuperchainWETHMetaData.ABI - -// SuperchainWETH is an auto generated Go binding around an Ethereum contract. -type SuperchainWETH struct { - SuperchainWETHCaller // Read-only binding to the contract - SuperchainWETHTransactor // Write-only binding to the contract - SuperchainWETHFilterer // Log filterer for contract events -} - -// SuperchainWETHCaller is an auto generated read-only Go binding around an Ethereum contract. -type SuperchainWETHCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// SuperchainWETHTransactor is an auto generated write-only Go binding around an Ethereum contract. -type SuperchainWETHTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// SuperchainWETHFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type SuperchainWETHFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// SuperchainWETHSession is an auto generated Go binding around an Ethereum contract, -// with pre-set call and transact options. -type SuperchainWETHSession struct { - Contract *SuperchainWETH // Generic contract binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// SuperchainWETHCallerSession is an auto generated read-only Go binding around an Ethereum contract, -// with pre-set call options. -type SuperchainWETHCallerSession struct { - Contract *SuperchainWETHCaller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session -} - -// SuperchainWETHTransactorSession is an auto generated write-only Go binding around an Ethereum contract, -// with pre-set transact options. -type SuperchainWETHTransactorSession struct { - Contract *SuperchainWETHTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// SuperchainWETHRaw is an auto generated low-level Go binding around an Ethereum contract. -type SuperchainWETHRaw struct { - Contract *SuperchainWETH // Generic contract binding to access the raw methods on -} - -// SuperchainWETHCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. -type SuperchainWETHCallerRaw struct { - Contract *SuperchainWETHCaller // Generic read-only contract binding to access the raw methods on -} - -// SuperchainWETHTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. -type SuperchainWETHTransactorRaw struct { - Contract *SuperchainWETHTransactor // Generic write-only contract binding to access the raw methods on -} - -// NewSuperchainWETH creates a new instance of SuperchainWETH, bound to a specific deployed contract. -func NewSuperchainWETH(address common.Address, backend bind.ContractBackend) (*SuperchainWETH, error) { - contract, err := bindSuperchainWETH(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &SuperchainWETH{SuperchainWETHCaller: SuperchainWETHCaller{contract: contract}, SuperchainWETHTransactor: SuperchainWETHTransactor{contract: contract}, SuperchainWETHFilterer: SuperchainWETHFilterer{contract: contract}}, nil -} - -// NewSuperchainWETHCaller creates a new read-only instance of SuperchainWETH, bound to a specific deployed contract. -func NewSuperchainWETHCaller(address common.Address, caller bind.ContractCaller) (*SuperchainWETHCaller, error) { - contract, err := bindSuperchainWETH(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &SuperchainWETHCaller{contract: contract}, nil -} - -// NewSuperchainWETHTransactor creates a new write-only instance of SuperchainWETH, bound to a specific deployed contract. -func NewSuperchainWETHTransactor(address common.Address, transactor bind.ContractTransactor) (*SuperchainWETHTransactor, error) { - contract, err := bindSuperchainWETH(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &SuperchainWETHTransactor{contract: contract}, nil -} - -// NewSuperchainWETHFilterer creates a new log filterer instance of SuperchainWETH, bound to a specific deployed contract. -func NewSuperchainWETHFilterer(address common.Address, filterer bind.ContractFilterer) (*SuperchainWETHFilterer, error) { - contract, err := bindSuperchainWETH(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &SuperchainWETHFilterer{contract: contract}, nil -} - -// bindSuperchainWETH binds a generic wrapper to an already deployed contract. -func bindSuperchainWETH(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := abi.JSON(strings.NewReader(SuperchainWETHABI)) - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_SuperchainWETH *SuperchainWETHRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _SuperchainWETH.Contract.SuperchainWETHCaller.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_SuperchainWETH *SuperchainWETHRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _SuperchainWETH.Contract.SuperchainWETHTransactor.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_SuperchainWETH *SuperchainWETHRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _SuperchainWETH.Contract.SuperchainWETHTransactor.contract.Transact(opts, method, params...) -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_SuperchainWETH *SuperchainWETHCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _SuperchainWETH.Contract.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_SuperchainWETH *SuperchainWETHTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _SuperchainWETH.Contract.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_SuperchainWETH *SuperchainWETHTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _SuperchainWETH.Contract.contract.Transact(opts, method, params...) -} - -// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. -// -// Solidity: function allowance(address owner, address spender) view returns(uint256) -func (_SuperchainWETH *SuperchainWETHCaller) Allowance(opts *bind.CallOpts, owner common.Address, spender common.Address) (*big.Int, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "allowance", owner, spender) - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. -// -// Solidity: function allowance(address owner, address spender) view returns(uint256) -func (_SuperchainWETH *SuperchainWETHSession) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { - return _SuperchainWETH.Contract.Allowance(&_SuperchainWETH.CallOpts, owner, spender) -} - -// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. -// -// Solidity: function allowance(address owner, address spender) view returns(uint256) -func (_SuperchainWETH *SuperchainWETHCallerSession) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { - return _SuperchainWETH.Contract.Allowance(&_SuperchainWETH.CallOpts, owner, spender) -} - -// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. -// -// Solidity: function balanceOf(address src) view returns(uint256) -func (_SuperchainWETH *SuperchainWETHCaller) BalanceOf(opts *bind.CallOpts, src common.Address) (*big.Int, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "balanceOf", src) - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. -// -// Solidity: function balanceOf(address src) view returns(uint256) -func (_SuperchainWETH *SuperchainWETHSession) BalanceOf(src common.Address) (*big.Int, error) { - return _SuperchainWETH.Contract.BalanceOf(&_SuperchainWETH.CallOpts, src) -} - -// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. -// -// Solidity: function balanceOf(address src) view returns(uint256) -func (_SuperchainWETH *SuperchainWETHCallerSession) BalanceOf(src common.Address) (*big.Int, error) { - return _SuperchainWETH.Contract.BalanceOf(&_SuperchainWETH.CallOpts, src) -} - -// Decimals is a free data retrieval call binding the contract method 0x313ce567. -// -// Solidity: function decimals() view returns(uint8) -func (_SuperchainWETH *SuperchainWETHCaller) Decimals(opts *bind.CallOpts) (uint8, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "decimals") - - if err != nil { - return *new(uint8), err - } - - out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) - - return out0, err - -} - -// Decimals is a free data retrieval call binding the contract method 0x313ce567. -// -// Solidity: function decimals() view returns(uint8) -func (_SuperchainWETH *SuperchainWETHSession) Decimals() (uint8, error) { - return _SuperchainWETH.Contract.Decimals(&_SuperchainWETH.CallOpts) -} - -// Decimals is a free data retrieval call binding the contract method 0x313ce567. -// -// Solidity: function decimals() view returns(uint8) -func (_SuperchainWETH *SuperchainWETHCallerSession) Decimals() (uint8, error) { - return _SuperchainWETH.Contract.Decimals(&_SuperchainWETH.CallOpts) -} - -// Name is a free data retrieval call binding the contract method 0x06fdde03. -// -// Solidity: function name() view returns(string) -func (_SuperchainWETH *SuperchainWETHCaller) Name(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "name") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// Name is a free data retrieval call binding the contract method 0x06fdde03. -// -// Solidity: function name() view returns(string) -func (_SuperchainWETH *SuperchainWETHSession) Name() (string, error) { - return _SuperchainWETH.Contract.Name(&_SuperchainWETH.CallOpts) -} - -// Name is a free data retrieval call binding the contract method 0x06fdde03. -// -// Solidity: function name() view returns(string) -func (_SuperchainWETH *SuperchainWETHCallerSession) Name() (string, error) { - return _SuperchainWETH.Contract.Name(&_SuperchainWETH.CallOpts) -} - -// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. -// -// Solidity: function supportsInterface(bytes4 _interfaceId) view returns(bool) -func (_SuperchainWETH *SuperchainWETHCaller) SupportsInterface(opts *bind.CallOpts, _interfaceId [4]byte) (bool, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "supportsInterface", _interfaceId) - - if err != nil { - return *new(bool), err - } - - out0 := *abi.ConvertType(out[0], new(bool)).(*bool) - - return out0, err - -} - -// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. -// -// Solidity: function supportsInterface(bytes4 _interfaceId) view returns(bool) -func (_SuperchainWETH *SuperchainWETHSession) SupportsInterface(_interfaceId [4]byte) (bool, error) { - return _SuperchainWETH.Contract.SupportsInterface(&_SuperchainWETH.CallOpts, _interfaceId) -} - -// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. -// -// Solidity: function supportsInterface(bytes4 _interfaceId) view returns(bool) -func (_SuperchainWETH *SuperchainWETHCallerSession) SupportsInterface(_interfaceId [4]byte) (bool, error) { - return _SuperchainWETH.Contract.SupportsInterface(&_SuperchainWETH.CallOpts, _interfaceId) -} - -// Symbol is a free data retrieval call binding the contract method 0x95d89b41. -// -// Solidity: function symbol() view returns(string) -func (_SuperchainWETH *SuperchainWETHCaller) Symbol(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "symbol") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// Symbol is a free data retrieval call binding the contract method 0x95d89b41. -// -// Solidity: function symbol() view returns(string) -func (_SuperchainWETH *SuperchainWETHSession) Symbol() (string, error) { - return _SuperchainWETH.Contract.Symbol(&_SuperchainWETH.CallOpts) -} - -// Symbol is a free data retrieval call binding the contract method 0x95d89b41. -// -// Solidity: function symbol() view returns(string) -func (_SuperchainWETH *SuperchainWETHCallerSession) Symbol() (string, error) { - return _SuperchainWETH.Contract.Symbol(&_SuperchainWETH.CallOpts) -} - -// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. -// -// Solidity: function totalSupply() view returns(uint256) -func (_SuperchainWETH *SuperchainWETHCaller) TotalSupply(opts *bind.CallOpts) (*big.Int, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "totalSupply") - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. -// -// Solidity: function totalSupply() view returns(uint256) -func (_SuperchainWETH *SuperchainWETHSession) TotalSupply() (*big.Int, error) { - return _SuperchainWETH.Contract.TotalSupply(&_SuperchainWETH.CallOpts) -} - -// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. -// -// Solidity: function totalSupply() view returns(uint256) -func (_SuperchainWETH *SuperchainWETHCallerSession) TotalSupply() (*big.Int, error) { - return _SuperchainWETH.Contract.TotalSupply(&_SuperchainWETH.CallOpts) -} - -// Version is a free data retrieval call binding the contract method 0x54fd4d50. -// -// Solidity: function version() view returns(string) -func (_SuperchainWETH *SuperchainWETHCaller) Version(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "version") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// Version is a free data retrieval call binding the contract method 0x54fd4d50. -// -// Solidity: function version() view returns(string) -func (_SuperchainWETH *SuperchainWETHSession) Version() (string, error) { - return _SuperchainWETH.Contract.Version(&_SuperchainWETH.CallOpts) -} - -// Version is a free data retrieval call binding the contract method 0x54fd4d50. -// -// Solidity: function version() view returns(string) -func (_SuperchainWETH *SuperchainWETHCallerSession) Version() (string, error) { - return _SuperchainWETH.Contract.Version(&_SuperchainWETH.CallOpts) -} - -// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. -// -// Solidity: function approve(address guy, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHTransactor) Approve(opts *bind.TransactOpts, guy common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "approve", guy, wad) -} - -// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. -// -// Solidity: function approve(address guy, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHSession) Approve(guy common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Approve(&_SuperchainWETH.TransactOpts, guy, wad) -} - -// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. -// -// Solidity: function approve(address guy, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHTransactorSession) Approve(guy common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Approve(&_SuperchainWETH.TransactOpts, guy, wad) -} - -// CrosschainBurn is a paid mutator transaction binding the contract method 0x2b8c49e3. -// -// Solidity: function crosschainBurn(address _from, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactor) CrosschainBurn(opts *bind.TransactOpts, _from common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "crosschainBurn", _from, _amount) -} - -// CrosschainBurn is a paid mutator transaction binding the contract method 0x2b8c49e3. -// -// Solidity: function crosschainBurn(address _from, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHSession) CrosschainBurn(_from common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.CrosschainBurn(&_SuperchainWETH.TransactOpts, _from, _amount) -} - -// CrosschainBurn is a paid mutator transaction binding the contract method 0x2b8c49e3. -// -// Solidity: function crosschainBurn(address _from, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactorSession) CrosschainBurn(_from common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.CrosschainBurn(&_SuperchainWETH.TransactOpts, _from, _amount) -} - -// CrosschainMint is a paid mutator transaction binding the contract method 0x18bf5077. -// -// Solidity: function crosschainMint(address _to, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactor) CrosschainMint(opts *bind.TransactOpts, _to common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "crosschainMint", _to, _amount) -} - -// CrosschainMint is a paid mutator transaction binding the contract method 0x18bf5077. -// -// Solidity: function crosschainMint(address _to, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHSession) CrosschainMint(_to common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.CrosschainMint(&_SuperchainWETH.TransactOpts, _to, _amount) -} - -// CrosschainMint is a paid mutator transaction binding the contract method 0x18bf5077. -// -// Solidity: function crosschainMint(address _to, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactorSession) CrosschainMint(_to common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.CrosschainMint(&_SuperchainWETH.TransactOpts, _to, _amount) -} - -// Deposit is a paid mutator transaction binding the contract method 0xd0e30db0. -// -// Solidity: function deposit() payable returns() -func (_SuperchainWETH *SuperchainWETHTransactor) Deposit(opts *bind.TransactOpts) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "deposit") -} - -// Deposit is a paid mutator transaction binding the contract method 0xd0e30db0. -// -// Solidity: function deposit() payable returns() -func (_SuperchainWETH *SuperchainWETHSession) Deposit() (*types.Transaction, error) { - return _SuperchainWETH.Contract.Deposit(&_SuperchainWETH.TransactOpts) -} - -// Deposit is a paid mutator transaction binding the contract method 0xd0e30db0. -// -// Solidity: function deposit() payable returns() -func (_SuperchainWETH *SuperchainWETHTransactorSession) Deposit() (*types.Transaction, error) { - return _SuperchainWETH.Contract.Deposit(&_SuperchainWETH.TransactOpts) -} - -// RelayETH is a paid mutator transaction binding the contract method 0x4f0edcc9. -// -// Solidity: function relayETH(address _from, address _to, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactor) RelayETH(opts *bind.TransactOpts, _from common.Address, _to common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "relayETH", _from, _to, _amount) -} - -// RelayETH is a paid mutator transaction binding the contract method 0x4f0edcc9. -// -// Solidity: function relayETH(address _from, address _to, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHSession) RelayETH(_from common.Address, _to common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.RelayETH(&_SuperchainWETH.TransactOpts, _from, _to, _amount) -} - -// RelayETH is a paid mutator transaction binding the contract method 0x4f0edcc9. -// -// Solidity: function relayETH(address _from, address _to, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactorSession) RelayETH(_from common.Address, _to common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.RelayETH(&_SuperchainWETH.TransactOpts, _from, _to, _amount) -} - -// SendETH is a paid mutator transaction binding the contract method 0x64a197f3. -// -// Solidity: function sendETH(address _to, uint256 _chainId) payable returns(bytes32 msgHash_) -func (_SuperchainWETH *SuperchainWETHTransactor) SendETH(opts *bind.TransactOpts, _to common.Address, _chainId *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "sendETH", _to, _chainId) -} - -// SendETH is a paid mutator transaction binding the contract method 0x64a197f3. -// -// Solidity: function sendETH(address _to, uint256 _chainId) payable returns(bytes32 msgHash_) -func (_SuperchainWETH *SuperchainWETHSession) SendETH(_to common.Address, _chainId *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.SendETH(&_SuperchainWETH.TransactOpts, _to, _chainId) -} - -// SendETH is a paid mutator transaction binding the contract method 0x64a197f3. -// -// Solidity: function sendETH(address _to, uint256 _chainId) payable returns(bytes32 msgHash_) -func (_SuperchainWETH *SuperchainWETHTransactorSession) SendETH(_to common.Address, _chainId *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.SendETH(&_SuperchainWETH.TransactOpts, _to, _chainId) -} - -// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. -// -// Solidity: function transfer(address dst, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHTransactor) Transfer(opts *bind.TransactOpts, dst common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "transfer", dst, wad) -} - -// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. -// -// Solidity: function transfer(address dst, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHSession) Transfer(dst common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Transfer(&_SuperchainWETH.TransactOpts, dst, wad) -} - -// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. -// -// Solidity: function transfer(address dst, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHTransactorSession) Transfer(dst common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Transfer(&_SuperchainWETH.TransactOpts, dst, wad) -} - -// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. -// -// Solidity: function transferFrom(address src, address dst, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHTransactor) TransferFrom(opts *bind.TransactOpts, src common.Address, dst common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "transferFrom", src, dst, wad) -} - -// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. -// -// Solidity: function transferFrom(address src, address dst, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHSession) TransferFrom(src common.Address, dst common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.TransferFrom(&_SuperchainWETH.TransactOpts, src, dst, wad) -} - -// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. -// -// Solidity: function transferFrom(address src, address dst, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHTransactorSession) TransferFrom(src common.Address, dst common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.TransferFrom(&_SuperchainWETH.TransactOpts, src, dst, wad) -} - -// Withdraw is a paid mutator transaction binding the contract method 0x2e1a7d4d. -// -// Solidity: function withdraw(uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactor) Withdraw(opts *bind.TransactOpts, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "withdraw", _amount) -} - -// Withdraw is a paid mutator transaction binding the contract method 0x2e1a7d4d. -// -// Solidity: function withdraw(uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHSession) Withdraw(_amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Withdraw(&_SuperchainWETH.TransactOpts, _amount) -} - -// Withdraw is a paid mutator transaction binding the contract method 0x2e1a7d4d. -// -// Solidity: function withdraw(uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactorSession) Withdraw(_amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Withdraw(&_SuperchainWETH.TransactOpts, _amount) -} - -// Fallback is a paid mutator transaction binding the contract fallback function. -// -// Solidity: fallback() payable returns() -func (_SuperchainWETH *SuperchainWETHTransactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) { - return _SuperchainWETH.contract.RawTransact(opts, calldata) -} - -// Fallback is a paid mutator transaction binding the contract fallback function. -// -// Solidity: fallback() payable returns() -func (_SuperchainWETH *SuperchainWETHSession) Fallback(calldata []byte) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Fallback(&_SuperchainWETH.TransactOpts, calldata) -} - -// Fallback is a paid mutator transaction binding the contract fallback function. -// -// Solidity: fallback() payable returns() -func (_SuperchainWETH *SuperchainWETHTransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Fallback(&_SuperchainWETH.TransactOpts, calldata) -} - -// Receive is a paid mutator transaction binding the contract receive function. -// -// Solidity: receive() payable returns() -func (_SuperchainWETH *SuperchainWETHTransactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) { - return _SuperchainWETH.contract.RawTransact(opts, nil) // calldata is disallowed for receive function -} - -// Receive is a paid mutator transaction binding the contract receive function. -// -// Solidity: receive() payable returns() -func (_SuperchainWETH *SuperchainWETHSession) Receive() (*types.Transaction, error) { - return _SuperchainWETH.Contract.Receive(&_SuperchainWETH.TransactOpts) -} - -// Receive is a paid mutator transaction binding the contract receive function. -// -// Solidity: receive() payable returns() -func (_SuperchainWETH *SuperchainWETHTransactorSession) Receive() (*types.Transaction, error) { - return _SuperchainWETH.Contract.Receive(&_SuperchainWETH.TransactOpts) -} - -// SuperchainWETHApprovalIterator is returned from FilterApproval and is used to iterate over the raw logs and unpacked data for Approval events raised by the SuperchainWETH contract. -type SuperchainWETHApprovalIterator struct { - Event *SuperchainWETHApproval // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHApprovalIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHApproval) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHApproval) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHApprovalIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHApprovalIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHApproval represents a Approval event raised by the SuperchainWETH contract. -type SuperchainWETHApproval struct { - Src common.Address - Guy common.Address - Wad *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterApproval is a free log retrieval operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. -// -// Solidity: event Approval(address indexed src, address indexed guy, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterApproval(opts *bind.FilterOpts, src []common.Address, guy []common.Address) (*SuperchainWETHApprovalIterator, error) { - - var srcRule []interface{} - for _, srcItem := range src { - srcRule = append(srcRule, srcItem) - } - var guyRule []interface{} - for _, guyItem := range guy { - guyRule = append(guyRule, guyItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "Approval", srcRule, guyRule) - if err != nil { - return nil, err - } - return &SuperchainWETHApprovalIterator{contract: _SuperchainWETH.contract, event: "Approval", logs: logs, sub: sub}, nil -} - -// WatchApproval is a free log subscription operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. -// -// Solidity: event Approval(address indexed src, address indexed guy, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchApproval(opts *bind.WatchOpts, sink chan<- *SuperchainWETHApproval, src []common.Address, guy []common.Address) (event.Subscription, error) { - - var srcRule []interface{} - for _, srcItem := range src { - srcRule = append(srcRule, srcItem) - } - var guyRule []interface{} - for _, guyItem := range guy { - guyRule = append(guyRule, guyItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "Approval", srcRule, guyRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHApproval) - if err := _SuperchainWETH.contract.UnpackLog(event, "Approval", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseApproval is a log parse operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. -// -// Solidity: event Approval(address indexed src, address indexed guy, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseApproval(log types.Log) (*SuperchainWETHApproval, error) { - event := new(SuperchainWETHApproval) - if err := _SuperchainWETH.contract.UnpackLog(event, "Approval", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// SuperchainWETHCrosschainBurnIterator is returned from FilterCrosschainBurn and is used to iterate over the raw logs and unpacked data for CrosschainBurn events raised by the SuperchainWETH contract. -type SuperchainWETHCrosschainBurnIterator struct { - Event *SuperchainWETHCrosschainBurn // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHCrosschainBurnIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHCrosschainBurn) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHCrosschainBurn) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHCrosschainBurnIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHCrosschainBurnIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHCrosschainBurn represents a CrosschainBurn event raised by the SuperchainWETH contract. -type SuperchainWETHCrosschainBurn struct { - From common.Address - Amount *big.Int - Sender common.Address - Raw types.Log // Blockchain specific contextual infos -} - -// FilterCrosschainBurn is a free log retrieval operation binding the contract event 0xb90795a66650155983e242cac3e1ac1a4dc26f8ed2987f3ce416a34e00111fd4. -// -// Solidity: event CrosschainBurn(address indexed from, uint256 amount, address indexed sender) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterCrosschainBurn(opts *bind.FilterOpts, from []common.Address, sender []common.Address) (*SuperchainWETHCrosschainBurnIterator, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - - var senderRule []interface{} - for _, senderItem := range sender { - senderRule = append(senderRule, senderItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "CrosschainBurn", fromRule, senderRule) - if err != nil { - return nil, err - } - return &SuperchainWETHCrosschainBurnIterator{contract: _SuperchainWETH.contract, event: "CrosschainBurn", logs: logs, sub: sub}, nil -} - -// WatchCrosschainBurn is a free log subscription operation binding the contract event 0xb90795a66650155983e242cac3e1ac1a4dc26f8ed2987f3ce416a34e00111fd4. -// -// Solidity: event CrosschainBurn(address indexed from, uint256 amount, address indexed sender) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchCrosschainBurn(opts *bind.WatchOpts, sink chan<- *SuperchainWETHCrosschainBurn, from []common.Address, sender []common.Address) (event.Subscription, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - - var senderRule []interface{} - for _, senderItem := range sender { - senderRule = append(senderRule, senderItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "CrosschainBurn", fromRule, senderRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHCrosschainBurn) - if err := _SuperchainWETH.contract.UnpackLog(event, "CrosschainBurn", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseCrosschainBurn is a log parse operation binding the contract event 0xb90795a66650155983e242cac3e1ac1a4dc26f8ed2987f3ce416a34e00111fd4. -// -// Solidity: event CrosschainBurn(address indexed from, uint256 amount, address indexed sender) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseCrosschainBurn(log types.Log) (*SuperchainWETHCrosschainBurn, error) { - event := new(SuperchainWETHCrosschainBurn) - if err := _SuperchainWETH.contract.UnpackLog(event, "CrosschainBurn", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// SuperchainWETHCrosschainMintIterator is returned from FilterCrosschainMint and is used to iterate over the raw logs and unpacked data for CrosschainMint events raised by the SuperchainWETH contract. -type SuperchainWETHCrosschainMintIterator struct { - Event *SuperchainWETHCrosschainMint // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHCrosschainMintIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHCrosschainMint) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHCrosschainMint) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHCrosschainMintIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHCrosschainMintIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHCrosschainMint represents a CrosschainMint event raised by the SuperchainWETH contract. -type SuperchainWETHCrosschainMint struct { - To common.Address - Amount *big.Int - Sender common.Address - Raw types.Log // Blockchain specific contextual infos -} - -// FilterCrosschainMint is a free log retrieval operation binding the contract event 0xde22baff038e3a3e08407cbdf617deed74e869a7ba517df611e33131c6e6ea04. -// -// Solidity: event CrosschainMint(address indexed to, uint256 amount, address indexed sender) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterCrosschainMint(opts *bind.FilterOpts, to []common.Address, sender []common.Address) (*SuperchainWETHCrosschainMintIterator, error) { - - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - var senderRule []interface{} - for _, senderItem := range sender { - senderRule = append(senderRule, senderItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "CrosschainMint", toRule, senderRule) - if err != nil { - return nil, err - } - return &SuperchainWETHCrosschainMintIterator{contract: _SuperchainWETH.contract, event: "CrosschainMint", logs: logs, sub: sub}, nil -} - -// WatchCrosschainMint is a free log subscription operation binding the contract event 0xde22baff038e3a3e08407cbdf617deed74e869a7ba517df611e33131c6e6ea04. -// -// Solidity: event CrosschainMint(address indexed to, uint256 amount, address indexed sender) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchCrosschainMint(opts *bind.WatchOpts, sink chan<- *SuperchainWETHCrosschainMint, to []common.Address, sender []common.Address) (event.Subscription, error) { - - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - var senderRule []interface{} - for _, senderItem := range sender { - senderRule = append(senderRule, senderItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "CrosschainMint", toRule, senderRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHCrosschainMint) - if err := _SuperchainWETH.contract.UnpackLog(event, "CrosschainMint", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseCrosschainMint is a log parse operation binding the contract event 0xde22baff038e3a3e08407cbdf617deed74e869a7ba517df611e33131c6e6ea04. -// -// Solidity: event CrosschainMint(address indexed to, uint256 amount, address indexed sender) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseCrosschainMint(log types.Log) (*SuperchainWETHCrosschainMint, error) { - event := new(SuperchainWETHCrosschainMint) - if err := _SuperchainWETH.contract.UnpackLog(event, "CrosschainMint", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// SuperchainWETHDepositIterator is returned from FilterDeposit and is used to iterate over the raw logs and unpacked data for Deposit events raised by the SuperchainWETH contract. -type SuperchainWETHDepositIterator struct { - Event *SuperchainWETHDeposit // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHDepositIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHDeposit) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHDeposit) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHDepositIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHDepositIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHDeposit represents a Deposit event raised by the SuperchainWETH contract. -type SuperchainWETHDeposit struct { - Dst common.Address - Wad *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterDeposit is a free log retrieval operation binding the contract event 0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c. -// -// Solidity: event Deposit(address indexed dst, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterDeposit(opts *bind.FilterOpts, dst []common.Address) (*SuperchainWETHDepositIterator, error) { - - var dstRule []interface{} - for _, dstItem := range dst { - dstRule = append(dstRule, dstItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "Deposit", dstRule) - if err != nil { - return nil, err - } - return &SuperchainWETHDepositIterator{contract: _SuperchainWETH.contract, event: "Deposit", logs: logs, sub: sub}, nil -} - -// WatchDeposit is a free log subscription operation binding the contract event 0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c. -// -// Solidity: event Deposit(address indexed dst, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchDeposit(opts *bind.WatchOpts, sink chan<- *SuperchainWETHDeposit, dst []common.Address) (event.Subscription, error) { - - var dstRule []interface{} - for _, dstItem := range dst { - dstRule = append(dstRule, dstItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "Deposit", dstRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHDeposit) - if err := _SuperchainWETH.contract.UnpackLog(event, "Deposit", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseDeposit is a log parse operation binding the contract event 0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c. -// -// Solidity: event Deposit(address indexed dst, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseDeposit(log types.Log) (*SuperchainWETHDeposit, error) { - event := new(SuperchainWETHDeposit) - if err := _SuperchainWETH.contract.UnpackLog(event, "Deposit", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// SuperchainWETHRelayETHIterator is returned from FilterRelayETH and is used to iterate over the raw logs and unpacked data for RelayETH events raised by the SuperchainWETH contract. -type SuperchainWETHRelayETHIterator struct { - Event *SuperchainWETHRelayETH // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHRelayETHIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHRelayETH) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHRelayETH) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHRelayETHIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHRelayETHIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHRelayETH represents a RelayETH event raised by the SuperchainWETH contract. -type SuperchainWETHRelayETH struct { - From common.Address - To common.Address - Amount *big.Int - Source *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterRelayETH is a free log retrieval operation binding the contract event 0xe5479bb8ebad3b9ac81f55f424a6289cf0a54ff2641708f41dcb2b26f264d359. -// -// Solidity: event RelayETH(address indexed from, address indexed to, uint256 amount, uint256 source) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterRelayETH(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*SuperchainWETHRelayETHIterator, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "RelayETH", fromRule, toRule) - if err != nil { - return nil, err - } - return &SuperchainWETHRelayETHIterator{contract: _SuperchainWETH.contract, event: "RelayETH", logs: logs, sub: sub}, nil -} - -// WatchRelayETH is a free log subscription operation binding the contract event 0xe5479bb8ebad3b9ac81f55f424a6289cf0a54ff2641708f41dcb2b26f264d359. -// -// Solidity: event RelayETH(address indexed from, address indexed to, uint256 amount, uint256 source) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchRelayETH(opts *bind.WatchOpts, sink chan<- *SuperchainWETHRelayETH, from []common.Address, to []common.Address) (event.Subscription, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "RelayETH", fromRule, toRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHRelayETH) - if err := _SuperchainWETH.contract.UnpackLog(event, "RelayETH", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseRelayETH is a log parse operation binding the contract event 0xe5479bb8ebad3b9ac81f55f424a6289cf0a54ff2641708f41dcb2b26f264d359. -// -// Solidity: event RelayETH(address indexed from, address indexed to, uint256 amount, uint256 source) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseRelayETH(log types.Log) (*SuperchainWETHRelayETH, error) { - event := new(SuperchainWETHRelayETH) - if err := _SuperchainWETH.contract.UnpackLog(event, "RelayETH", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// SuperchainWETHSendETHIterator is returned from FilterSendETH and is used to iterate over the raw logs and unpacked data for SendETH events raised by the SuperchainWETH contract. -type SuperchainWETHSendETHIterator struct { - Event *SuperchainWETHSendETH // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHSendETHIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHSendETH) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHSendETH) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHSendETHIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHSendETHIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHSendETH represents a SendETH event raised by the SuperchainWETH contract. -type SuperchainWETHSendETH struct { - From common.Address - To common.Address - Amount *big.Int - Destination *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterSendETH is a free log retrieval operation binding the contract event 0xed98a2ff78833375c368471a747cdf0633024dde3f870feb08a934ac5be83402. -// -// Solidity: event SendETH(address indexed from, address indexed to, uint256 amount, uint256 destination) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterSendETH(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*SuperchainWETHSendETHIterator, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "SendETH", fromRule, toRule) - if err != nil { - return nil, err - } - return &SuperchainWETHSendETHIterator{contract: _SuperchainWETH.contract, event: "SendETH", logs: logs, sub: sub}, nil -} - -// WatchSendETH is a free log subscription operation binding the contract event 0xed98a2ff78833375c368471a747cdf0633024dde3f870feb08a934ac5be83402. -// -// Solidity: event SendETH(address indexed from, address indexed to, uint256 amount, uint256 destination) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchSendETH(opts *bind.WatchOpts, sink chan<- *SuperchainWETHSendETH, from []common.Address, to []common.Address) (event.Subscription, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "SendETH", fromRule, toRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHSendETH) - if err := _SuperchainWETH.contract.UnpackLog(event, "SendETH", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseSendETH is a log parse operation binding the contract event 0xed98a2ff78833375c368471a747cdf0633024dde3f870feb08a934ac5be83402. -// -// Solidity: event SendETH(address indexed from, address indexed to, uint256 amount, uint256 destination) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseSendETH(log types.Log) (*SuperchainWETHSendETH, error) { - event := new(SuperchainWETHSendETH) - if err := _SuperchainWETH.contract.UnpackLog(event, "SendETH", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// SuperchainWETHTransferIterator is returned from FilterTransfer and is used to iterate over the raw logs and unpacked data for Transfer events raised by the SuperchainWETH contract. -type SuperchainWETHTransferIterator struct { - Event *SuperchainWETHTransfer // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHTransferIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHTransfer) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHTransfer) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHTransferIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHTransferIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHTransfer represents a Transfer event raised by the SuperchainWETH contract. -type SuperchainWETHTransfer struct { - Src common.Address - Dst common.Address - Wad *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterTransfer is a free log retrieval operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. -// -// Solidity: event Transfer(address indexed src, address indexed dst, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterTransfer(opts *bind.FilterOpts, src []common.Address, dst []common.Address) (*SuperchainWETHTransferIterator, error) { - - var srcRule []interface{} - for _, srcItem := range src { - srcRule = append(srcRule, srcItem) - } - var dstRule []interface{} - for _, dstItem := range dst { - dstRule = append(dstRule, dstItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "Transfer", srcRule, dstRule) - if err != nil { - return nil, err - } - return &SuperchainWETHTransferIterator{contract: _SuperchainWETH.contract, event: "Transfer", logs: logs, sub: sub}, nil -} - -// WatchTransfer is a free log subscription operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. -// -// Solidity: event Transfer(address indexed src, address indexed dst, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *SuperchainWETHTransfer, src []common.Address, dst []common.Address) (event.Subscription, error) { - - var srcRule []interface{} - for _, srcItem := range src { - srcRule = append(srcRule, srcItem) - } - var dstRule []interface{} - for _, dstItem := range dst { - dstRule = append(dstRule, dstItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "Transfer", srcRule, dstRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHTransfer) - if err := _SuperchainWETH.contract.UnpackLog(event, "Transfer", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseTransfer is a log parse operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. -// -// Solidity: event Transfer(address indexed src, address indexed dst, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseTransfer(log types.Log) (*SuperchainWETHTransfer, error) { - event := new(SuperchainWETHTransfer) - if err := _SuperchainWETH.contract.UnpackLog(event, "Transfer", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// SuperchainWETHWithdrawalIterator is returned from FilterWithdrawal and is used to iterate over the raw logs and unpacked data for Withdrawal events raised by the SuperchainWETH contract. -type SuperchainWETHWithdrawalIterator struct { - Event *SuperchainWETHWithdrawal // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHWithdrawalIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHWithdrawal) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHWithdrawal) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHWithdrawalIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHWithdrawalIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHWithdrawal represents a Withdrawal event raised by the SuperchainWETH contract. -type SuperchainWETHWithdrawal struct { - Src common.Address - Wad *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterWithdrawal is a free log retrieval operation binding the contract event 0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65. -// -// Solidity: event Withdrawal(address indexed src, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterWithdrawal(opts *bind.FilterOpts, src []common.Address) (*SuperchainWETHWithdrawalIterator, error) { - - var srcRule []interface{} - for _, srcItem := range src { - srcRule = append(srcRule, srcItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "Withdrawal", srcRule) - if err != nil { - return nil, err - } - return &SuperchainWETHWithdrawalIterator{contract: _SuperchainWETH.contract, event: "Withdrawal", logs: logs, sub: sub}, nil -} - -// WatchWithdrawal is a free log subscription operation binding the contract event 0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65. -// -// Solidity: event Withdrawal(address indexed src, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchWithdrawal(opts *bind.WatchOpts, sink chan<- *SuperchainWETHWithdrawal, src []common.Address) (event.Subscription, error) { - - var srcRule []interface{} - for _, srcItem := range src { - srcRule = append(srcRule, srcItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "Withdrawal", srcRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHWithdrawal) - if err := _SuperchainWETH.contract.UnpackLog(event, "Withdrawal", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseWithdrawal is a log parse operation binding the contract event 0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65. -// -// Solidity: event Withdrawal(address indexed src, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseWithdrawal(log types.Log) (*SuperchainWETHWithdrawal, error) { - event := new(SuperchainWETHWithdrawal) - if err := _SuperchainWETH.contract.UnpackLog(event, "Withdrawal", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} diff --git a/devnet-sdk/contracts/constants/constants.go b/devnet-sdk/contracts/constants/constants.go deleted file mode 100644 index 67801db723dde..0000000000000 --- a/devnet-sdk/contracts/constants/constants.go +++ /dev/null @@ -1,55 +0,0 @@ -package constants - -import ( - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/common" -) - -var ( - LegacyMessagePasser types.Address = common.HexToAddress("0x4200000000000000000000000000000000000000") - DeployerWhitelist types.Address = common.HexToAddress("0x4200000000000000000000000000000000000002") - WETH types.Address = common.HexToAddress("0x4200000000000000000000000000000000000006") - L2CrossDomainMessenger types.Address = common.HexToAddress("0x4200000000000000000000000000000000000007") - GasPriceOracle types.Address = common.HexToAddress("0x420000000000000000000000000000000000000F") - L2StandardBridge types.Address = common.HexToAddress("0x4200000000000000000000000000000000000010") - SequencerFeeVault types.Address = common.HexToAddress("0x4200000000000000000000000000000000000011") - OptimismMintableERC20Factory types.Address = common.HexToAddress("0x4200000000000000000000000000000000000012") - L1BlockNumber types.Address = common.HexToAddress("0x4200000000000000000000000000000000000013") - L1Block types.Address = common.HexToAddress("0x4200000000000000000000000000000000000015") - L2ToL1MessagePasser types.Address = common.HexToAddress("0x4200000000000000000000000000000000000016") - L2ERC721Bridge types.Address = common.HexToAddress("0x4200000000000000000000000000000000000014") - OptimismMintableERC721Factory types.Address = common.HexToAddress("0x4200000000000000000000000000000000000017") - ProxyAdmin types.Address = common.HexToAddress("0x4200000000000000000000000000000000000018") - BaseFeeVault types.Address = common.HexToAddress("0x4200000000000000000000000000000000000019") - L1FeeVault types.Address = common.HexToAddress("0x420000000000000000000000000000000000001a") - OperatorFeeVault types.Address = common.HexToAddress("0x420000000000000000000000000000000000001B") - SchemaRegistry types.Address = common.HexToAddress("0x4200000000000000000000000000000000000020") - EAS types.Address = common.HexToAddress("0x4200000000000000000000000000000000000021") - CrossL2Inbox types.Address = common.HexToAddress("0x4200000000000000000000000000000000000022") - L2ToL2CrossDomainMessenger types.Address = common.HexToAddress("0x4200000000000000000000000000000000000023") - SuperchainETHBridge types.Address = common.HexToAddress("0x4200000000000000000000000000000000000024") - ETHLiquidity types.Address = common.HexToAddress("0x4200000000000000000000000000000000000025") - SuperchainTokenBridge types.Address = common.HexToAddress("0x4200000000000000000000000000000000000028") - NativeAssetLiquidity types.Address = common.HexToAddress("0x4200000000000000000000000000000000000029") - LiquidityController types.Address = common.HexToAddress("0x420000000000000000000000000000000000002a") - FeeSplitter types.Address = common.HexToAddress("0x420000000000000000000000000000000000002B") - GovernanceToken types.Address = common.HexToAddress("0x4200000000000000000000000000000000000042") - Create2Deployer types.Address = common.HexToAddress("0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2") - MultiCall3 types.Address = common.HexToAddress("0xcA11bde05977b3631167028862bE2a173976CA11") - Safe_v130 types.Address = common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938") - SafeL2_v130 types.Address = common.HexToAddress("0xfb1bffC9d739B8D520DaF37dF666da4C687191EA") - MultiSendCallOnly_v130 types.Address = common.HexToAddress("0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B") - SafeSingletonFactory types.Address = common.HexToAddress("0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7") - DeterministicDeploymentProxy types.Address = common.HexToAddress("0x4e59b44847b379578588920cA78FbF26c0B4956C") - MultiSend_v130 types.Address = common.HexToAddress("0x998739BFdAAdde7C933B942a68053933098f9EDa") - Permit2 types.Address = common.HexToAddress("0x000000000022D473030F116dDEE9F6B43aC78BA3") - SenderCreator_v060 types.Address = common.HexToAddress("0x7fc98430eaedbb6070b35b39d798725049088348") - EntryPoint_v060 types.Address = common.HexToAddress("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789") - SenderCreator_v070 types.Address = common.HexToAddress("0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C") - EntryPoint_v070 types.Address = common.HexToAddress("0x0000000071727De22E5E9d8BAf0edAc6f37da032") -) - -const ( - ETH = 1e18 - Gwei = 1e9 -) diff --git a/devnet-sdk/contracts/contracts.go b/devnet-sdk/contracts/contracts.go deleted file mode 100644 index ad2e55dc2d471..0000000000000 --- a/devnet-sdk/contracts/contracts.go +++ /dev/null @@ -1,17 +0,0 @@ -package contracts - -import ( - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/registry/client" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/registry/empty" - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum/go-ethereum/ethclient" -) - -// NewClientRegistry creates a new Registry that uses the provided client -func NewClientRegistry(c *ethclient.Client) interfaces.ContractsRegistry { - return &client.ClientRegistry{Client: c} -} - -func NewEmptyRegistry() interfaces.ContractsRegistry { - return &empty.EmptyRegistry{} -} diff --git a/devnet-sdk/contracts/registry/client/client.go b/devnet-sdk/contracts/registry/client/client.go deleted file mode 100644 index d60f35639e6b0..0000000000000 --- a/devnet-sdk/contracts/registry/client/client.go +++ /dev/null @@ -1,50 +0,0 @@ -package client - -import ( - "fmt" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/ethclient" -) - -// ClientRegistry is a Registry implementation that uses an ethclient.Client -type ClientRegistry struct { - Client *ethclient.Client -} - -var _ interfaces.ContractsRegistry = (*ClientRegistry)(nil) - -func (r *ClientRegistry) WETH(address types.Address) (interfaces.WETH, error) { - // SuperchainWETH was removed and replaced with SuperchainETHBridge - // NewSuperchainWETH can be still used for fetching WETH balance - binding, err := bindings.NewSuperchainWETH(address, r.Client) - if err != nil { - return nil, fmt.Errorf("failed to create WETH binding: %w", err) - } - return &WETHBinding{ - contractAddress: address, - client: r.Client, - binding: binding, - }, nil -} - -func (r *ClientRegistry) L2ToL2CrossDomainMessenger(address types.Address) (interfaces.L2ToL2CrossDomainMessenger, error) { - binding, err := bindings.NewL2ToL2CrossDomainMessenger(address, r.Client) - if err != nil { - return nil, fmt.Errorf("failed to create L2ToL2CrossDomainMessenger binding: %w", err) - } - abi, err := abi.JSON(strings.NewReader(bindings.L2ToL2CrossDomainMessengerMetaData.ABI)) - if err != nil { - return nil, fmt.Errorf("failed to create L2ToL2CrossDomainMessenger binding ABI: %w", err) - } - return &L2ToL2CrossDomainMessengerBinding{ - contractAddress: address, - client: r.Client, - binding: binding, - abi: &abi, - }, nil -} diff --git a/devnet-sdk/contracts/registry/client/l2tol2crossdomainmessenger.go b/devnet-sdk/contracts/registry/client/l2tol2crossdomainmessenger.go deleted file mode 100644 index f2ff3da7ab7e8..0000000000000 --- a/devnet-sdk/contracts/registry/client/l2tol2crossdomainmessenger.go +++ /dev/null @@ -1,22 +0,0 @@ -package client - -import ( - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/ethclient" -) - -type L2ToL2CrossDomainMessengerBinding struct { - contractAddress types.Address - client *ethclient.Client - binding *bindings.L2ToL2CrossDomainMessenger - abi *abi.ABI -} - -var _ interfaces.L2ToL2CrossDomainMessenger = (*L2ToL2CrossDomainMessengerBinding)(nil) - -func (b *L2ToL2CrossDomainMessengerBinding) ABI() *abi.ABI { - return b.abi -} diff --git a/devnet-sdk/contracts/registry/client/weth.go b/devnet-sdk/contracts/registry/client/weth.go deleted file mode 100644 index 68d30af479ce5..0000000000000 --- a/devnet-sdk/contracts/registry/client/weth.go +++ /dev/null @@ -1,38 +0,0 @@ -package client - -import ( - "context" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/ethclient" -) - -type WETHBinding struct { - contractAddress types.Address - client *ethclient.Client - binding *bindings.SuperchainWETH -} - -var _ interfaces.WETH = (*WETHBinding)(nil) - -func (b *WETHBinding) BalanceOf(addr types.Address) types.ReadInvocation[types.Balance] { - return &WETHBalanceOfImpl{ - contract: b, - addr: addr, - } -} - -type WETHBalanceOfImpl struct { - contract *WETHBinding - addr types.Address -} - -func (i *WETHBalanceOfImpl) Call(ctx context.Context) (types.Balance, error) { - balance, err := i.contract.binding.BalanceOf(nil, i.addr) - if err != nil { - return types.Balance{}, err - } - return types.NewBalance(balance), nil -} diff --git a/devnet-sdk/contracts/registry/empty/empty.go b/devnet-sdk/contracts/registry/empty/empty.go deleted file mode 100644 index e5534908fd76c..0000000000000 --- a/devnet-sdk/contracts/registry/empty/empty.go +++ /dev/null @@ -1,25 +0,0 @@ -package empty - -import ( - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" -) - -// EmptyRegistry represents a registry that returns not found errors for all contract accesses -type EmptyRegistry struct{} - -var _ interfaces.ContractsRegistry = (*EmptyRegistry)(nil) - -func (r *EmptyRegistry) WETH(address types.Address) (interfaces.WETH, error) { - return nil, &interfaces.ErrContractNotFound{ - ContractType: "WETH", - Address: address, - } -} - -func (r *EmptyRegistry) L2ToL2CrossDomainMessenger(address types.Address) (interfaces.L2ToL2CrossDomainMessenger, error) { - return nil, &interfaces.ErrContractNotFound{ - ContractType: "L2ToL2CrossDomainMessenger", - Address: address, - } -} diff --git a/devnet-sdk/controller/kt/kt.go b/devnet-sdk/controller/kt/kt.go deleted file mode 100644 index 7e2d338b9bf80..0000000000000 --- a/devnet-sdk/controller/kt/kt.go +++ /dev/null @@ -1,109 +0,0 @@ -package kt - -import ( - "context" - "fmt" - "strings" - "sync" - - "github.com/ethereum-optimism/optimism/devnet-sdk/controller/surface" - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/run" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/wrappers" -) - -type KurtosisControllerSurface struct { - env *descriptors.DevnetEnvironment - kurtosisCtx interfaces.KurtosisContextInterface - runner *run.KurtosisRunner - devnetfs *fs.DevnetFS - - // control operations are disruptive, let's make sure we don't run them - // concurrently so that test logic has a fighting chance of being correct. - mtx sync.Mutex -} - -func NewKurtosisControllerSurface(env *descriptors.DevnetEnvironment) (*KurtosisControllerSurface, error) { - enclave := env.Name - - kurtosisCtx, err := wrappers.GetDefaultKurtosisContext() - if err != nil { - return nil, err - } - - runner, err := run.NewKurtosisRunner( - run.WithKurtosisRunnerEnclave(enclave), - run.WithKurtosisRunnerKurtosisContext(kurtosisCtx), - ) - if err != nil { - return nil, err - } - - enclaveFS, err := fs.NewEnclaveFS(context.TODO(), enclave) - if err != nil { - return nil, err - } - // Create a new DevnetFS instance using the enclaveFS - devnetfs := fs.NewDevnetFS(enclaveFS) - - return &KurtosisControllerSurface{ - env: env, - kurtosisCtx: kurtosisCtx, - runner: runner, - devnetfs: devnetfs, - }, nil -} - -func (s *KurtosisControllerSurface) StartService(ctx context.Context, serviceName string) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - script := fmt.Sprintf(` -def run(plan): - plan.start_service(name="%s") -`, serviceName) - // start_service is not idempotent, and doesn't return a typed error, - // so we need to check the error message - if err := s.runner.RunScript(ctx, script); err != nil { - msg := err.Error() - if strings.Contains(strings.ToLower(msg), "is already in use by container") { - // we know we don't need to update the env, as the service was already running - return nil - } - return err - } - return s.updateDevnetEnvironmentForService(ctx, serviceName, true) -} - -func (s *KurtosisControllerSurface) StopService(ctx context.Context, serviceName string) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - script := fmt.Sprintf(` -def run(plan): - plan.stop_service(name="%s") -`, serviceName) - // stop_service is idempotent, so errors here are real - if err := s.runner.RunScript(ctx, script); err != nil { - return err - } - // conversely, we don't know if the service was running or not, so we need to update the env - return s.updateDevnetEnvironmentForService(ctx, serviceName, false) -} - -func (s *KurtosisControllerSurface) updateDevnetEnvironmentForService(ctx context.Context, serviceName string, on bool) error { - // - refreshed, err := s.updateDevnetEnvironmentService(ctx, serviceName, on) - if err != nil { - return err - } - if !refreshed { - return nil - } - - return s.devnetfs.UploadDevnetDescriptor(ctx, s.env) -} - -var _ surface.ServiceLifecycleSurface = (*KurtosisControllerSurface)(nil) diff --git a/devnet-sdk/controller/kt/kt_test.go b/devnet-sdk/controller/kt/kt_test.go deleted file mode 100644 index c84452102882c..0000000000000 --- a/devnet-sdk/controller/kt/kt_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package kt - -import ( - "context" - "errors" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/fake" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/run" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestKurtosisControllerSurface(t *testing.T) { - ctx := context.Background() - testErr := errors.New("test error") - - // Create a test environment - env := &descriptors.DevnetEnvironment{ - Name: "test-env", - L1: &descriptors.Chain{ - Services: descriptors.RedundantServiceMap{ - "test-service": []*descriptors.Service{ - &descriptors.Service{ - Name: "test-service", - Endpoints: descriptors.EndpointMap{ - "http": { - Port: 0, - PrivatePort: 0, - }, - }, - }, - }, - }, - }, - } - - // Create a test service context with port data - testSvcCtx := &testServiceContext{ - publicPorts: map[string]interfaces.PortSpec{ - "http": &testPortSpec{number: 8080}, - }, - privatePorts: map[string]interfaces.PortSpec{ - "http": &testPortSpec{number: 8082}, - }, - } - - tests := []struct { - name string - serviceName string - operation string // "start" or "stop" - runErr error - wantErr bool - }{ - { - name: "successful service start", - serviceName: "test-service", - operation: "start", - runErr: nil, - wantErr: false, - }, - { - name: "service already running", - serviceName: "test-service", - operation: "start", - runErr: errors.New("is already in use by container"), - wantErr: false, - }, - { - name: "error starting service", - serviceName: "test-service", - operation: "start", - runErr: testErr, - wantErr: true, - }, - { - name: "successful service stop", - serviceName: "test-service", - operation: "stop", - runErr: nil, - wantErr: false, - }, - { - name: "error stopping service", - serviceName: "test-service", - operation: "stop", - runErr: testErr, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create a fake enclave context that will return our test service context - fakeEnclaveCtx := &fake.EnclaveContext{ - RunErr: tt.runErr, - Services: map[services.ServiceName]interfaces.ServiceContext{ - "test-service": testSvcCtx, - }, - } - - // Create a fake Kurtosis context that will return our fake enclave context - fakeCtx := &fake.KurtosisContext{ - EnclaveCtx: fakeEnclaveCtx, - } - - // Create a KurtosisRunner with our fake context - runner, err := run.NewKurtosisRunner( - run.WithKurtosisRunnerEnclave("test-enclave"), - run.WithKurtosisRunnerKurtosisContext(fakeCtx), - ) - require.NoError(t, err) - - // Create the controller surface with all required fields - surface := &KurtosisControllerSurface{ - env: env, - kurtosisCtx: fakeCtx, - runner: runner, - } - - // Create the mock DevnetFS - mockDevnetFS, err := newMockDevnetFS(env) - require.NoError(t, err) - surface.devnetfs = mockDevnetFS - - switch tt.operation { - case "start": - err = surface.StartService(ctx, tt.serviceName) - case "stop": - err = surface.StopService(ctx, tt.serviceName) - default: - t.Fatalf("unknown operation: %s", tt.operation) - } - - if tt.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - - // For successful start operations, verify that the service endpoints were updated - if tt.operation == "start" && !tt.wantErr { - svc := findSvcInEnv(env, tt.serviceName) - require.NotNil(t, svc) - require.Equal(t, 8080, svc[0].Endpoints["http"].Port) - require.Equal(t, 8082, svc[0].Endpoints["http"].PrivatePort) - } - }) - } -} diff --git a/devnet-sdk/controller/kt/mutate_env.go b/devnet-sdk/controller/kt/mutate_env.go deleted file mode 100644 index 66c5c8cde27cf..0000000000000 --- a/devnet-sdk/controller/kt/mutate_env.go +++ /dev/null @@ -1,100 +0,0 @@ -package kt - -import ( - "context" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" -) - -// a hack because some L2 services are duplicated across chains -type redundantService []*descriptors.Service - -func (s redundantService) getEndpoints() descriptors.EndpointMap { - if len(s) == 0 { - return nil - } - - return s[0].Endpoints -} - -func (s redundantService) setEndpoints(endpoints descriptors.EndpointMap) { - for _, svc := range s { - svc.Endpoints = endpoints - } -} - -func (s redundantService) refreshEndpoints(serviceCtx interfaces.ServiceContext) { - endpoints := s.getEndpoints() - - publicPorts := serviceCtx.GetPublicPorts() - privatePorts := serviceCtx.GetPrivatePorts() - - for name, info := range publicPorts { - endpoints[name].Port = int(info.GetNumber()) - } - for name, info := range privatePorts { - endpoints[name].PrivatePort = int(info.GetNumber()) - } - - s.setEndpoints(endpoints) -} - -func findSvcInEnv(env *descriptors.DevnetEnvironment, serviceName string) redundantService { - if svc := findSvcInChain(env.L1, serviceName); svc != nil { - return redundantService{svc} - } - - var services redundantService = nil - for _, l2 := range env.L2 { - if svc := findSvcInChain(l2.Chain, serviceName); svc != nil { - services = append(services, svc) - } - } - return services -} - -func findSvcInChain(chain *descriptors.Chain, serviceName string) *descriptors.Service { - for _, instances := range chain.Services { - for _, svc := range instances { - if svc.Name == serviceName { - return svc - } - } - } - - for _, node := range chain.Nodes { - for _, svc := range node.Services { - if svc.Name == serviceName { - return svc - } - } - } - - return nil -} - -func (s *KurtosisControllerSurface) updateDevnetEnvironmentService(ctx context.Context, serviceName string, on bool) (bool, error) { - svc := findSvcInEnv(s.env, serviceName) - if svc == nil { - // service is not part of the env, so we don't need to do anything - return false, nil - } - - // get the enclave - enclaveCtx, err := s.kurtosisCtx.GetEnclave(ctx, s.env.Name) - if err != nil { - return false, err - } - - serviceCtx, err := enclaveCtx.GetService(serviceName) - if err != nil { - return false, err - } - - if on { - svc.refreshEndpoints(serviceCtx) - } - // otherwise the service is down anyway, it doesn't matter if it has outdated endpoints - return on, nil -} diff --git a/devnet-sdk/controller/kt/mutate_env_test.go b/devnet-sdk/controller/kt/mutate_env_test.go deleted file mode 100644 index 3a83dc585a948..0000000000000 --- a/devnet-sdk/controller/kt/mutate_env_test.go +++ /dev/null @@ -1,152 +0,0 @@ -package kt - -import ( - "context" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" - "github.com/stretchr/testify/require" -) - -func TestRedundantServiceRefreshEndpoints(t *testing.T) { - // Create a test service with some initial endpoints - svc1 := &descriptors.Service{ - Name: "test-service", - Endpoints: descriptors.EndpointMap{ - "http": { - Port: 0, - PrivatePort: 0, - }, - "ws": { - Port: 0, - PrivatePort: 0, - }, - }, - } - svc2 := &descriptors.Service{ - Name: "test-service", - Endpoints: descriptors.EndpointMap{ - "http": { - Port: 0, - PrivatePort: 0, - }, - "ws": { - Port: 0, - PrivatePort: 0, - }, - }, - } - - // Create a redundant service with both services - redundant := redundantService{svc1, svc2} - - // Create a test service context with new port numbers - testCtx := &testServiceContext{ - publicPorts: map[string]interfaces.PortSpec{ - "http": &testPortSpec{number: 8080}, - "ws": &testPortSpec{number: 8081}, - }, - privatePorts: map[string]interfaces.PortSpec{ - "http": &testPortSpec{number: 8082}, - "ws": &testPortSpec{number: 8083}, - }, - } - - // Call refreshEndpoints - redundant.refreshEndpoints(testCtx) - - // Verify that both services have been updated with the new port numbers - for _, svc := range redundant { - require.Equal(t, 8080, svc.Endpoints["http"].Port) - require.Equal(t, 8081, svc.Endpoints["ws"].Port) - require.Equal(t, 8082, svc.Endpoints["http"].PrivatePort) - require.Equal(t, 8083, svc.Endpoints["ws"].PrivatePort) - } -} - -func TestRedundantServiceEmpty(t *testing.T) { - // Test behavior with empty redundant service - redundant := redundantService{} - testCtx := &testServiceContext{ - publicPorts: map[string]interfaces.PortSpec{}, - privatePorts: map[string]interfaces.PortSpec{}, - } - - // This should not panic - redundant.refreshEndpoints(testCtx) -} - -func TestUpdateDevnetEnvironmentService(t *testing.T) { - // Create a test environment with a service - env := &descriptors.DevnetEnvironment{ - Name: "test-env", - L1: &descriptors.Chain{ - Services: descriptors.RedundantServiceMap{ - "test-service": []*descriptors.Service{ - &descriptors.Service{ - Name: "test-service", - Endpoints: descriptors.EndpointMap{ - "http": { - Port: 0, - PrivatePort: 0, - }, - }, - }, - }, - }, - }, - } - - // Create a test service context with new port numbers - testSvcCtx := &testServiceContext{ - publicPorts: map[string]interfaces.PortSpec{ - "http": &testPortSpec{number: 8080}, - }, - privatePorts: map[string]interfaces.PortSpec{ - "http": &testPortSpec{number: 8082}, - }, - } - - // Create a mock enclave context with our service - mockEnclave := &mockEnclaveContext{ - services: map[services.ServiceName]interfaces.ServiceContext{ - "test-service": testSvcCtx, - }, - } - - // Create a mock kurtosis context with our enclave - mockKurtosisCtx := &mockKurtosisContext{ - enclaves: map[string]interfaces.EnclaveContext{ - "test-env": mockEnclave, - }, - } - - // Create the controller surface - controller := &KurtosisControllerSurface{ - kurtosisCtx: mockKurtosisCtx, - env: env, - } - - // Create the mock DevnetFS - mockDevnetFS, err := newMockDevnetFS(env) - require.NoError(t, err) - controller.devnetfs = mockDevnetFS - - // Test updating the service (turning it on) - updated, err := controller.updateDevnetEnvironmentService(context.Background(), "test-service", true) - require.NoError(t, err) - require.True(t, updated) - - // Verify that the service's endpoints were updated - svc := findSvcInEnv(env, "test-service") - require.NotNil(t, svc) - require.Equal(t, 8080, svc[0].Endpoints["http"].Port) - require.Equal(t, 8082, svc[0].Endpoints["http"].PrivatePort) - - // Test updating a non-existent service - updated, err = controller.updateDevnetEnvironmentService(context.Background(), "non-existent-service", true) - require.NoError(t, err) - require.False(t, updated) -} diff --git a/devnet-sdk/controller/kt/test_helpers.go b/devnet-sdk/controller/kt/test_helpers.go deleted file mode 100644 index f61df2f1fcff4..0000000000000 --- a/devnet-sdk/controller/kt/test_helpers.go +++ /dev/null @@ -1,145 +0,0 @@ -package kt - -import ( - "context" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/enclaves" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/starlark_run_config" - "github.com/spf13/afero" -) - -// testPortSpec implements interfaces.PortSpec -type testPortSpec struct { - number uint16 -} - -func (m *testPortSpec) GetNumber() uint16 { - return m.number -} - -// testServiceContext implements interfaces.ServiceContext -type testServiceContext struct { - publicPorts map[string]interfaces.PortSpec - privatePorts map[string]interfaces.PortSpec -} - -func (m *testServiceContext) GetServiceUUID() services.ServiceUUID { - return "mock-service-uuid" -} - -func (m *testServiceContext) GetMaybePublicIPAddress() string { - return "127.0.0.1" -} - -func (m *testServiceContext) GetPublicPorts() map[string]interfaces.PortSpec { - return m.publicPorts -} - -func (m *testServiceContext) GetPrivatePorts() map[string]interfaces.PortSpec { - return m.privatePorts -} - -func (m *testServiceContext) GetLabels() map[string]string { - return make(map[string]string) -} - -// mockEnclaveFS implements fs.EnclaveContextIface for testing -type mockEnclaveFS struct { - env *descriptors.DevnetEnvironment -} - -func (m *mockEnclaveFS) GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) { - return nil, nil -} - -func (m *mockEnclaveFS) DownloadFilesArtifact(ctx context.Context, name string) ([]byte, error) { - return nil, nil -} - -func (m *mockEnclaveFS) UploadFiles(pathToUpload string, artifactName string) (services.FilesArtifactUUID, services.FileArtifactName, error) { - return "", "", nil -} - -// newMockDevnetFS creates a new mock DevnetFS for testing -func newMockDevnetFS(env *descriptors.DevnetEnvironment) (*fs.DevnetFS, error) { - mockEnclaveFS := &mockEnclaveFS{env: env} - enclaveFS, err := fs.NewEnclaveFS(context.Background(), "test-enclave", - fs.WithEnclaveCtx(mockEnclaveFS), - fs.WithFs(afero.NewMemMapFs()), - ) - if err != nil { - return nil, err - } - return fs.NewDevnetFS(enclaveFS), nil -} - -type mockEnclaveContext struct { - services map[services.ServiceName]interfaces.ServiceContext -} - -func (m *mockEnclaveContext) GetEnclaveUuid() enclaves.EnclaveUUID { - return "mock-enclave-uuid" -} - -func (m *mockEnclaveContext) GetService(serviceIdentifier string) (interfaces.ServiceContext, error) { - if svc, ok := m.services[services.ServiceName(serviceIdentifier)]; ok { - return svc, nil - } - return nil, nil -} - -func (m *mockEnclaveContext) GetServices() (map[services.ServiceName]services.ServiceUUID, error) { - result := make(map[services.ServiceName]services.ServiceUUID) - for name, svc := range m.services { - result[name] = svc.GetServiceUUID() - } - return result, nil -} - -func (m *mockEnclaveContext) GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) { - return nil, nil -} - -func (m *mockEnclaveContext) RunStarlarkPackage(ctx context.Context, pkg string, serializedParams *starlark_run_config.StarlarkRunConfig) (<-chan interfaces.StarlarkResponse, string, error) { - return nil, "", nil -} - -func (m *mockEnclaveContext) RunStarlarkScript(ctx context.Context, script string, serializedParams *starlark_run_config.StarlarkRunConfig) error { - return nil -} - -// mockKurtosisContext implements interfaces.KurtosisContextInterface -type mockKurtosisContext struct { - enclaves map[string]interfaces.EnclaveContext -} - -func (m *mockKurtosisContext) CreateEnclave(ctx context.Context, name string) (interfaces.EnclaveContext, error) { - if enclave, ok := m.enclaves[name]; ok { - return enclave, nil - } - return nil, nil -} - -func (m *mockKurtosisContext) GetEnclave(ctx context.Context, name string) (interfaces.EnclaveContext, error) { - if enclave, ok := m.enclaves[name]; ok { - return enclave, nil - } - return nil, nil -} - -func (m *mockKurtosisContext) Clean(ctx context.Context, destroyAll bool) ([]interfaces.EnclaveNameAndUuid, error) { - return []interfaces.EnclaveNameAndUuid{}, nil -} - -func (m *mockKurtosisContext) GetEnclaveStatus(ctx context.Context, name string) (interfaces.EnclaveStatus, error) { - return interfaces.EnclaveStatusRunning, nil -} - -func (m *mockKurtosisContext) DestroyEnclave(ctx context.Context, name string) error { - return nil -} diff --git a/devnet-sdk/controller/surface/surface.go b/devnet-sdk/controller/surface/surface.go deleted file mode 100644 index 519d9d1c302b1..0000000000000 --- a/devnet-sdk/controller/surface/surface.go +++ /dev/null @@ -1,11 +0,0 @@ -package surface - -import "context" - -type ControlSurface interface { -} - -type ServiceLifecycleSurface interface { - StartService(context.Context, string) error - StopService(context.Context, string) error -} diff --git a/devnet-sdk/descriptors/deployment.go b/devnet-sdk/descriptors/deployment.go deleted file mode 100644 index 60e67ea2ced6a..0000000000000 --- a/devnet-sdk/descriptors/deployment.go +++ /dev/null @@ -1,87 +0,0 @@ -package descriptors - -import ( - "encoding/json" - "net/http" - - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum/go-ethereum/params" -) - -type PortInfo struct { - Host string `json:"host"` - Scheme string `json:"scheme,omitempty"` - Port int `json:"port,omitempty"` - PrivatePort int `json:"private_port,omitempty"` - - ReverseProxyHeader http.Header `json:"reverse_proxy_header,omitempty"` -} - -// EndpointMap is a map of service names to their endpoints. -type EndpointMap map[string]*PortInfo - -// Service represents a chain service (e.g. batcher, proposer, challenger) -type Service struct { - Name string `json:"name"` - Endpoints EndpointMap `json:"endpoints"` - Labels map[string]string `json:"labels,omitempty"` -} - -// ServiceMap is a map of service names to services. -type ServiceMap map[string]*Service - -// RedundantServiceMap is a map of service names to services. -// It is used to represent services that are redundant, i.e. they can have multiple instances. -type RedundantServiceMap map[string][]*Service - -// Node represents a node for a chain -type Node struct { - Name string `json:"name"` - Services ServiceMap `json:"services"` - Labels map[string]string `json:"labels,omitempty"` -} - -// AddressMap is a map of address names to their corresponding addresses -type AddressMap map[string]types.Address - -type Chain struct { - Name string `json:"name"` - ID string `json:"id,omitempty"` - Services RedundantServiceMap `json:"services,omitempty"` - Nodes []Node `json:"nodes"` - Wallets WalletMap `json:"wallets,omitempty"` - JWT string `json:"jwt,omitempty"` - Config *params.ChainConfig `json:"config,omitempty"` - Addresses AddressMap `json:"addresses,omitempty"` -} - -type L2Chain struct { - *Chain - L1Wallets WalletMap `json:"l1_wallets,omitempty"` - RollupConfig *rollup.Config `json:"rollup_config"` -} - -// Wallet represents a wallet with an address and optional private key. -type Wallet struct { - Address types.Address `json:"address"` - PrivateKey string `json:"private_key,omitempty"` -} - -// WalletMap is a map of wallet names to wallets. -type WalletMap map[string]*Wallet - -type DepSet = json.RawMessage - -// DevnetEnvironment exposes the relevant information to interact with a devnet. -type DevnetEnvironment struct { - Name string `json:"name"` - - ReverseProxyURL string `json:"reverse_proxy_url,omitempty"` - - L1 *Chain `json:"l1"` - L2 []*L2Chain `json:"l2"` - - Features []string `json:"features,omitempty"` - DepSets map[string]DepSet `json:"dep_sets,omitempty"` -} diff --git a/devnet-sdk/images/repository.go b/devnet-sdk/images/repository.go deleted file mode 100644 index 0766e0243a01f..0000000000000 --- a/devnet-sdk/images/repository.go +++ /dev/null @@ -1,43 +0,0 @@ -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" -) - -// 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, - "op-reth": opLabsToolsRegistry, - }, - } -} - -// 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/devnet-sdk/interfaces/registry.go b/devnet-sdk/interfaces/registry.go deleted file mode 100644 index 9ab21b04f1991..0000000000000 --- a/devnet-sdk/interfaces/registry.go +++ /dev/null @@ -1,33 +0,0 @@ -package interfaces - -import ( - "fmt" - - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi" -) - -// ErrContractNotFound indicates that a contract is not available at the requested address -type ErrContractNotFound struct { - ContractType string - Address types.Address -} - -func (e *ErrContractNotFound) Error() string { - return fmt.Sprintf("%s contract not found at %s", e.ContractType, e.Address) -} - -// ContractsRegistry provides access to all supported contract instances -type ContractsRegistry interface { - WETH(address types.Address) (WETH, error) - L2ToL2CrossDomainMessenger(address types.Address) (L2ToL2CrossDomainMessenger, error) -} - -// WETH represents the interface for interacting with the WETH contract -type WETH interface { - BalanceOf(user types.Address) types.ReadInvocation[types.Balance] -} - -type L2ToL2CrossDomainMessenger interface { - ABI() *abi.ABI -} diff --git a/devnet-sdk/kt/fs/devnet_fs.go b/devnet-sdk/kt/fs/devnet_fs.go deleted file mode 100644 index 9062728e8b452..0000000000000 --- a/devnet-sdk/kt/fs/devnet_fs.go +++ /dev/null @@ -1,171 +0,0 @@ -package fs - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "log" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" -) - -const ( - DevnetEnvArtifactNamePrefix = "devnet-descriptor-" - DevnetEnvArtifactPath = "env.json" -) - -type DevnetFS struct { - *EnclaveFS -} - -type DevnetFSDescriptorOption func(*options) - -type options struct { - artifactName string - artifactPath string -} - -func newOptions() *options { - return &options{ - artifactPath: DevnetEnvArtifactPath, - } -} - -func WithArtifactName(name string) DevnetFSDescriptorOption { - return func(o *options) { - o.artifactName = name - } -} - -func WithArtifactPath(path string) DevnetFSDescriptorOption { - return func(o *options) { - o.artifactPath = path - } -} - -func NewDevnetFS(fs *EnclaveFS) *DevnetFS { - return &DevnetFS{EnclaveFS: fs} -} - -func (fs *DevnetFS) GetDevnetDescriptor(ctx context.Context, opts ...DevnetFSDescriptorOption) (*descriptors.DevnetEnvironment, error) { - options := newOptions() - for _, opt := range opts { - opt(options) - } - - if options.artifactName == "" { - if err := fs.loadLatestDevnetDescriptorName(ctx, options); err != nil { - return nil, err - } - } - - artifact, err := fs.GetArtifact(ctx, options.artifactName) - if err != nil { - return nil, fmt.Errorf("error getting artifact: %w", err) - } - - var buf bytes.Buffer - writer := NewArtifactFileWriter(options.artifactPath, &buf) - - if err := artifact.ExtractFiles(writer); err != nil { - return nil, fmt.Errorf("error extracting file from artifact: %w", err) - } - - var env descriptors.DevnetEnvironment - if err := json.Unmarshal(buf.Bytes(), &env); err != nil { - return nil, fmt.Errorf("error unmarshalling environment: %w", err) - } - - return &env, nil -} - -func (fs *DevnetFS) UploadDevnetDescriptor(ctx context.Context, env *descriptors.DevnetEnvironment, opts ...DevnetFSDescriptorOption) error { - envBuf := bytes.NewBuffer(nil) - enc := json.NewEncoder(envBuf) - enc.SetIndent("", " ") - if err := enc.Encode(env); err != nil { - return fmt.Errorf("error encoding environment: %w", err) - } - - options := newOptions() - for _, opt := range opts { - opt(options) - } - - if options.artifactName == "" { - if err := fs.loadNextDevnetDescriptorName(ctx, options); err != nil { - return fmt.Errorf("error getting next devnet descriptor: %w", err) - } - } - - if err := fs.PutArtifact(ctx, options.artifactName, NewArtifactFileReader(options.artifactPath, envBuf)); err != nil { - return fmt.Errorf("error putting environment artifact: %w", err) - } - - return nil -} - -func (fs *DevnetFS) loadLatestDevnetDescriptorName(ctx context.Context, options *options) error { - names, err := fs.GetAllArtifactNames(ctx) - if err != nil { - return fmt.Errorf("error getting artifact names: %w", err) - } - - var maxSuffix int = -1 - var maxName string - for _, name := range names { - _, suffix, found := strings.Cut(name, DevnetEnvArtifactNamePrefix) - if !found { - continue - } - - // Parse the suffix as a number - var num int - if _, err := fmt.Sscanf(suffix, "%d", &num); err != nil { - continue // Skip if suffix is not a valid number - } - - // Update maxName if this number is larger - if num > maxSuffix { - maxSuffix = num - maxName = name - } - } - - if maxName == "" { - return fmt.Errorf("no descriptor found with valid numerical suffix") - } - - options.artifactName = maxName - return nil -} - -func (fs *DevnetFS) loadNextDevnetDescriptorName(ctx context.Context, options *options) error { - artifactNames, err := fs.GetAllArtifactNames(ctx) - if err != nil { - return fmt.Errorf("error getting artifact names: %w", err) - } - - maxNum := -1 - for _, artifactName := range artifactNames { - if !strings.HasPrefix(artifactName, DevnetEnvArtifactNamePrefix) { - continue - } - - numStr := strings.TrimPrefix(artifactName, DevnetEnvArtifactNamePrefix) - num := 0 - if _, err := fmt.Sscanf(numStr, "%d", &num); err != nil { - log.Printf("Warning: invalid devnet descriptor format: %s", artifactName) - continue - } - - if num > maxNum { - maxNum = num - } - } - - options.artifactName = fmt.Sprintf("%s%d", DevnetEnvArtifactNamePrefix, maxNum+1) - return nil -} diff --git a/devnet-sdk/kt/fs/devnet_fs_test.go b/devnet-sdk/kt/fs/devnet_fs_test.go deleted file mode 100644 index d8bb6824001f4..0000000000000 --- a/devnet-sdk/kt/fs/devnet_fs_test.go +++ /dev/null @@ -1,293 +0,0 @@ -package fs - -import ( - "context" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestGetDevnetDescriptor(t *testing.T) { - envContent := `{"name": "test-env", "l1": {"name": "l1", "id": "1", "nodes": []}, "l2": []}` - expectedEnv := &descriptors.DevnetEnvironment{ - Name: "test-env", - L1: &descriptors.Chain{ - Name: "l1", - ID: "1", - Nodes: []descriptors.Node{}, - }, - L2: []*descriptors.L2Chain{}, - } - - tests := []struct { - name string - artifactName string - artifactPath string - envContent string - wantErr bool - expectedEnv *descriptors.DevnetEnvironment - }{ - { - name: "successful retrieval with default path", - artifactName: "devnet-descriptor-1", - artifactPath: DevnetEnvArtifactPath, - envContent: envContent, - expectedEnv: expectedEnv, - }, - { - name: "successful retrieval with custom path", - artifactName: "devnet-descriptor-1", - artifactPath: "custom/path/env.json", - envContent: envContent, - expectedEnv: expectedEnv, - }, - { - name: "invalid json content", - artifactName: "devnet-descriptor-1", - artifactPath: DevnetEnvArtifactPath, - envContent: `invalid json`, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create mock context with artifact - mockCtx := &mockEnclaveContext{ - artifacts: map[string][]byte{ - tt.artifactName: createTarGzArtifact(t, map[string]string{ - tt.artifactPath: tt.envContent, - }), - }, - fs: afero.NewMemMapFs(), - } - - enclaveFS, err := NewEnclaveFS(context.Background(), "test-enclave", WithEnclaveCtx(mockCtx), WithFs(mockCtx.fs)) - require.NoError(t, err) - - devnetFS := NewDevnetFS(enclaveFS) - - // Get descriptor with options - opts := []DevnetFSDescriptorOption{} - if tt.artifactName != "" { - opts = append(opts, WithArtifactName(tt.artifactName)) - } - if tt.artifactPath != DevnetEnvArtifactPath { - opts = append(opts, WithArtifactPath(tt.artifactPath)) - } - - env, err := devnetFS.GetDevnetDescriptor(context.Background(), opts...) - if tt.wantErr { - assert.Error(t, err) - return - } - require.NoError(t, err) - assert.Equal(t, tt.expectedEnv, env) - }) - } -} - -func TestUploadDevnetDescriptor(t *testing.T) { - env := &descriptors.DevnetEnvironment{ - Name: "test-env", - L1: &descriptors.Chain{ - Name: "l1", - ID: "1", - Nodes: []descriptors.Node{}, - }, - L2: []*descriptors.L2Chain{}, - } - - tests := []struct { - name string - artifactName string - artifactPath string - env *descriptors.DevnetEnvironment - wantErr bool - }{ - { - name: "successful upload with default path", - artifactName: "devnet-descriptor-1", - artifactPath: DevnetEnvArtifactPath, - env: env, - }, - { - name: "successful upload with custom path", - artifactName: "devnet-descriptor-1", - artifactPath: "custom/path/env.json", - env: env, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create mock context - mockCtx := &mockEnclaveContext{ - artifacts: make(map[string][]byte), - fs: afero.NewMemMapFs(), - } - - enclaveFS, err := NewEnclaveFS(context.Background(), "test-enclave", WithEnclaveCtx(mockCtx), WithFs(mockCtx.fs)) - require.NoError(t, err) - - devnetFS := NewDevnetFS(enclaveFS) - - // Upload descriptor with options - opts := []DevnetFSDescriptorOption{} - if tt.artifactName != "" { - opts = append(opts, WithArtifactName(tt.artifactName)) - } - if tt.artifactPath != DevnetEnvArtifactPath { - opts = append(opts, WithArtifactPath(tt.artifactPath)) - } - - err = devnetFS.UploadDevnetDescriptor(context.Background(), tt.env, opts...) - if tt.wantErr { - assert.Error(t, err) - return - } - require.NoError(t, err) - - // Verify the artifact was uploaded - require.NotNil(t, mockCtx.uploaded) - uploaded := mockCtx.uploaded[tt.artifactName] - require.NotNil(t, uploaded) - require.Contains(t, uploaded, tt.artifactPath) - }) - } -} - -func TestLoadLatestDevnetDescriptorName(t *testing.T) { - tests := []struct { - name string - existingNames []string - expectedName string - wantErr bool - }{ - { - name: "single descriptor", - existingNames: []string{ - "devnet-descriptor-1", - }, - expectedName: "devnet-descriptor-1", - }, - { - name: "multiple descriptors", - existingNames: []string{ - "devnet-descriptor-1", - "devnet-descriptor-3", - "devnet-descriptor-2", - }, - expectedName: "devnet-descriptor-3", - }, - { - name: "no descriptors", - existingNames: []string{}, - wantErr: true, - }, - { - name: "invalid descriptor names", - existingNames: []string{ - "invalid-name", - "devnet-descriptor-invalid", - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create mock context with artifacts - mockCtx := &mockEnclaveContext{ - artifacts: make(map[string][]byte), - fs: afero.NewMemMapFs(), - } - - // Add artifacts to the mock context - for _, name := range tt.existingNames { - mockCtx.artifacts[name] = []byte{} - } - - enclaveFS, err := NewEnclaveFS(context.Background(), "test-enclave", WithEnclaveCtx(mockCtx), WithFs(mockCtx.fs)) - require.NoError(t, err) - - devnetFS := NewDevnetFS(enclaveFS) - - options := newOptions() - err = devnetFS.loadLatestDevnetDescriptorName(context.Background(), options) - if tt.wantErr { - assert.Error(t, err) - return - } - require.NoError(t, err) - assert.Equal(t, tt.expectedName, options.artifactName) - }) - } -} - -func TestLoadNextDevnetDescriptorName(t *testing.T) { - tests := []struct { - name string - existingNames []string - expectedName string - }{ - { - name: "no existing descriptors", - existingNames: []string{}, - expectedName: "devnet-descriptor-0", - }, - { - name: "single descriptor", - existingNames: []string{ - "devnet-descriptor-1", - }, - expectedName: "devnet-descriptor-2", - }, - { - name: "multiple descriptors", - existingNames: []string{ - "devnet-descriptor-1", - "devnet-descriptor-3", - "devnet-descriptor-2", - }, - expectedName: "devnet-descriptor-4", - }, - { - name: "with invalid descriptor names", - existingNames: []string{ - "invalid-name", - "devnet-descriptor-1", - "devnet-descriptor-invalid", - }, - expectedName: "devnet-descriptor-2", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create mock context with artifacts - mockCtx := &mockEnclaveContext{ - artifacts: make(map[string][]byte), - fs: afero.NewMemMapFs(), - } - - // Add artifacts to the mock context - for _, name := range tt.existingNames { - mockCtx.artifacts[name] = []byte{} - } - - enclaveFS, err := NewEnclaveFS(context.Background(), "test-enclave", WithEnclaveCtx(mockCtx), WithFs(mockCtx.fs)) - require.NoError(t, err) - - devnetFS := NewDevnetFS(enclaveFS) - - options := newOptions() - err = devnetFS.loadNextDevnetDescriptorName(context.Background(), options) - require.NoError(t, err) - assert.Equal(t, tt.expectedName, options.artifactName) - }) - } -} diff --git a/devnet-sdk/kt/fs/fs.go b/devnet-sdk/kt/fs/fs.go deleted file mode 100644 index f1acb7e12f851..0000000000000 --- a/devnet-sdk/kt/fs/fs.go +++ /dev/null @@ -1,272 +0,0 @@ -package fs - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "context" - "fmt" - "io" - "os" - "path/filepath" - - "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" - "github.com/kurtosis-tech/kurtosis/api/golang/engine/lib/kurtosis_context" - "github.com/spf13/afero" -) - -// EnclaveContextIface abstracts the EnclaveContext for testing -type EnclaveContextIface interface { - GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) - DownloadFilesArtifact(ctx context.Context, name string) ([]byte, error) - UploadFiles(pathToUpload string, artifactName string) (services.FilesArtifactUUID, services.FileArtifactName, error) -} - -type EnclaveFS struct { - enclaveCtx EnclaveContextIface - fs afero.Fs -} - -type EnclaveFSOption func(*EnclaveFS) - -func WithFs(fs afero.Fs) EnclaveFSOption { - return func(e *EnclaveFS) { - e.fs = fs - } -} - -func WithEnclaveCtx(enclaveCtx EnclaveContextIface) EnclaveFSOption { - return func(e *EnclaveFS) { - e.enclaveCtx = enclaveCtx - } -} - -func NewEnclaveFS(ctx context.Context, enclave string, opts ...EnclaveFSOption) (*EnclaveFS, error) { - enclaveFS := &EnclaveFS{} - - for _, opt := range opts { - opt(enclaveFS) - } - - if enclaveFS.fs == nil { - enclaveFS.fs = afero.NewOsFs() - } - - if enclaveFS.enclaveCtx == nil { - kurtosisCtx, err := kurtosis_context.NewKurtosisContextFromLocalEngine() - if err != nil { - return nil, err - } - - enclaveCtx, err := kurtosisCtx.GetEnclaveContext(ctx, enclave) - if err != nil { - return nil, err - } - - enclaveFS.enclaveCtx = enclaveCtx - } - - return enclaveFS, nil -} - -type Artifact struct { - rawData []byte - reader *tar.Reader - fs afero.Fs -} - -func (fs *EnclaveFS) GetAllArtifactNames(ctx context.Context) ([]string, error) { - artifacts, err := fs.enclaveCtx.GetAllFilesArtifactNamesAndUuids(ctx) - if err != nil { - return nil, err - } - - names := make([]string, len(artifacts)) - for i, artifact := range artifacts { - names[i] = artifact.GetFileName() - } - - return names, nil -} - -func (fs *EnclaveFS) GetArtifact(ctx context.Context, name string) (*Artifact, error) { - artifact, err := fs.enclaveCtx.DownloadFilesArtifact(ctx, name) - if err != nil { - return nil, err - } - - // Store the raw data - buffer := bytes.NewBuffer(artifact) - zipReader, err := gzip.NewReader(buffer) - if err != nil { - return nil, err - } - tarReader := tar.NewReader(zipReader) - return &Artifact{ - rawData: artifact, - reader: tarReader, - fs: fs.fs, - }, nil -} - -func (a *Artifact) newReader() (*tar.Reader, error) { - buffer := bytes.NewBuffer(a.rawData) - zipReader, err := gzip.NewReader(buffer) - if err != nil { - return nil, err - } - return tar.NewReader(zipReader), nil -} - -func (a *Artifact) Download(path string) error { - // Create a new reader for this operation - reader, err := a.newReader() - if err != nil { - return fmt.Errorf("failed to create reader: %w", err) - } - - for { - header, err := reader.Next() - if err == io.EOF { - return nil - } - if err != nil { - return fmt.Errorf("failed to read tar header: %w", err) - } - - fpath := filepath.Join(path, filepath.Clean(header.Name)) - - switch header.Typeflag { - case tar.TypeDir: - if err := a.fs.MkdirAll(fpath, os.FileMode(header.Mode)); err != nil { - return fmt.Errorf("failed to create directory %s: %w", fpath, err) - } - case tar.TypeReg: - // Create parent directories if they don't exist - if err := a.fs.MkdirAll(filepath.Dir(fpath), 0755); err != nil { - return fmt.Errorf("failed to create directory for %s: %w", fpath, err) - } - - // Create the file - f, err := a.fs.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, os.FileMode(header.Mode)) - if err != nil { - return fmt.Errorf("failed to create file %s: %w", fpath, err) - } - - // Copy contents from tar reader to file - if _, err := io.Copy(f, reader); err != nil { - f.Close() - return fmt.Errorf("failed to write contents to %s: %w", fpath, err) - } - f.Close() - default: - return fmt.Errorf("unsupported file type %d for %s", header.Typeflag, header.Name) - } - } -} - -func (a *Artifact) ExtractFiles(writers ...*ArtifactFileWriter) error { - // Create a new reader for this operation - reader, err := a.newReader() - if err != nil { - return fmt.Errorf("failed to create reader: %w", err) - } - - paths := make(map[string]io.Writer) - for _, writer := range writers { - canonicalPath := filepath.Clean(writer.path) - paths[canonicalPath] = writer.writer - } - - for { - header, err := reader.Next() - if err == io.EOF { - break - } - if err != nil { - return fmt.Errorf("failed to read tar header: %w", err) - } - - headerPath := filepath.Clean(header.Name) - if _, ok := paths[headerPath]; !ok { - continue - } - - writer := paths[headerPath] - _, err = io.Copy(writer, reader) - if err != nil { - return fmt.Errorf("failed to copy content: %w", err) - } - } - - return nil -} - -func (fs *EnclaveFS) PutArtifact(ctx context.Context, name string, readers ...*ArtifactFileReader) (retErr error) { - // Create a temporary directory using afero - tempDir, err := afero.TempDir(fs.fs, "", "artifact-*") - if err != nil { - return err - } - defer func() { - if err := fs.fs.RemoveAll(tempDir); err != nil && retErr == nil { - retErr = fmt.Errorf("failed to cleanup temporary directory: %w", err) - } - }() - - // Process each reader - for _, reader := range readers { - // Create the full path in the temp directory - fullPath := filepath.Join(tempDir, reader.path) - - // Ensure the parent directory exists - if err := fs.fs.MkdirAll(filepath.Dir(fullPath), 0755); err != nil { - return err - } - - // Create the file - file, err := fs.fs.Create(fullPath) - if err != nil { - return err - } - - // Copy the content - _, err = io.Copy(file, reader.reader) - file.Close() // Close file after writing - if err != nil { - return err - } - } - - // Upload the directory to Kurtosis - if _, _, err := fs.enclaveCtx.UploadFiles(tempDir, name); err != nil { - return err - } - - return -} - -type ArtifactFileReader struct { - path string - reader io.Reader -} - -func NewArtifactFileReader(path string, reader io.Reader) *ArtifactFileReader { - return &ArtifactFileReader{ - path: path, - reader: reader, - } -} - -type ArtifactFileWriter struct { - path string - writer io.Writer -} - -func NewArtifactFileWriter(path string, writer io.Writer) *ArtifactFileWriter { - return &ArtifactFileWriter{ - path: path, - writer: writer, - } -} diff --git a/devnet-sdk/kt/fs/fs_test.go b/devnet-sdk/kt/fs/fs_test.go deleted file mode 100644 index c347a75c4aa16..0000000000000 --- a/devnet-sdk/kt/fs/fs_test.go +++ /dev/null @@ -1,450 +0,0 @@ -package fs - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "context" - "os" - "path/filepath" - "testing" - - "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type mockEnclaveContext struct { - artifacts map[string][]byte - uploaded map[string]map[string][]byte // artifactName -> path -> content - fs afero.Fs // filesystem to use for operations -} - -func (m *mockEnclaveContext) DownloadFilesArtifact(_ context.Context, name string) ([]byte, error) { - return m.artifacts[name], nil -} - -func (m *mockEnclaveContext) UploadFiles(pathToUpload string, artifactName string) (services.FilesArtifactUUID, services.FileArtifactName, error) { - if m.uploaded == nil { - m.uploaded = make(map[string]map[string][]byte) - } - m.uploaded[artifactName] = make(map[string][]byte) - - err := afero.Walk(m.fs, pathToUpload, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } - - relPath, err := filepath.Rel(pathToUpload, path) - if err != nil { - return err - } - - content, err := afero.ReadFile(m.fs, path) - if err != nil { - return err - } - - m.uploaded[artifactName][relPath] = content - return nil - }) - - return "test-uuid", services.FileArtifactName(artifactName), err -} - -func (m *mockEnclaveContext) GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) { - var result []*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid - for name := range m.artifacts { - result = append(result, &kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid{ - FileName: name, - FileUuid: "test-uuid", - }) - } - return result, nil -} - -var _ EnclaveContextIface = (*mockEnclaveContext)(nil) - -func createTarGzArtifact(t *testing.T, files map[string]string) []byte { - var buf bytes.Buffer - gzWriter := gzip.NewWriter(&buf) - tarWriter := tar.NewWriter(gzWriter) - - for name, content := range files { - err := tarWriter.WriteHeader(&tar.Header{ - Name: name, - Mode: 0600, - Size: int64(len(content)), - }) - require.NoError(t, err) - - _, err = tarWriter.Write([]byte(content)) - require.NoError(t, err) - } - - require.NoError(t, tarWriter.Close()) - require.NoError(t, gzWriter.Close()) - return buf.Bytes() -} - -func TestArtifactExtraction(t *testing.T) { - tests := []struct { - name string - files map[string]string - requests map[string]string - wantErr bool - }{ - { - name: "simple path", - files: map[string]string{ - "file1.txt": "content1", - }, - requests: map[string]string{ - "file1.txt": "content1", - }, - }, - { - name: "path with dot prefix", - files: map[string]string{ - "./file1.txt": "content1", - }, - requests: map[string]string{ - "file1.txt": "content1", - }, - }, - { - name: "mixed paths", - files: map[string]string{ - "./file1.txt": "content1", - "file2.txt": "content2", - "./dir/f3.txt": "content3", - }, - requests: map[string]string{ - "file1.txt": "content1", - "file2.txt": "content2", - "dir/f3.txt": "content3", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create mock context with artifact - mockCtx := &mockEnclaveContext{ - artifacts: map[string][]byte{ - "test-artifact": createTarGzArtifact(t, tt.files), - }, - fs: afero.NewMemMapFs(), - } - - fs, err := NewEnclaveFS(context.Background(), "test-enclave", WithEnclaveCtx(mockCtx), WithFs(mockCtx.fs)) - require.NoError(t, err) - - artifact, err := fs.GetArtifact(context.Background(), "test-artifact") - require.NoError(t, err) - - // Create writers for all requested files - writers := make([]*ArtifactFileWriter, 0, len(tt.requests)) - buffers := make(map[string]*bytes.Buffer, len(tt.requests)) - for reqPath := range tt.requests { - buf := &bytes.Buffer{} - buffers[reqPath] = buf - writers = append(writers, NewArtifactFileWriter(reqPath, buf)) - } - - // Extract all files at once - err = artifact.ExtractFiles(writers...) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - - // Verify contents - for reqPath, wantContent := range tt.requests { - require.Equal(t, wantContent, buffers[reqPath].String(), "content mismatch for %s", reqPath) - } - }) - } -} - -func TestPutArtifact(t *testing.T) { - tests := []struct { - name string - files map[string]string - wantErr bool - }{ - { - name: "single file", - files: map[string]string{ - "file1.txt": "content1", - }, - }, - { - name: "multiple files", - files: map[string]string{ - "file1.txt": "content1", - "dir/file2.txt": "content2", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fs := afero.NewMemMapFs() - mockCtx := &mockEnclaveContext{ - artifacts: make(map[string][]byte), - fs: fs, - } - - enclaveFs := &EnclaveFS{ - enclaveCtx: mockCtx, - fs: fs, - } - - // Create readers for all files - var readers []*ArtifactFileReader - for path, content := range tt.files { - readers = append(readers, NewArtifactFileReader( - path, - bytes.NewReader([]byte(content)), - )) - } - - // Put the artifact - err := enclaveFs.PutArtifact(context.Background(), "test-artifact", readers...) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - - // Verify uploaded contents - require.NotNil(t, mockCtx.uploaded) - uploaded := mockCtx.uploaded["test-artifact"] - require.NotNil(t, uploaded) - require.Equal(t, len(tt.files), len(uploaded)) - - for path, wantContent := range tt.files { - content, exists := uploaded[path] - require.True(t, exists, "missing file: %s", path) - require.Equal(t, wantContent, string(content), "content mismatch for %s", path) - } - }) - } -} - -func TestMultipleExtractCalls(t *testing.T) { - // Create a test artifact with multiple files - files := map[string]string{ - "file1.txt": "content1", - "file2.txt": "content2", - "dir/file3.txt": "content3", - "dir/file4.txt": "content4", - } - - // Create mock context with artifact - mockCtx := &mockEnclaveContext{ - artifacts: map[string][]byte{ - "test-artifact": createTarGzArtifact(t, files), - }, - fs: afero.NewMemMapFs(), - } - - fs, err := NewEnclaveFS(context.Background(), "test-enclave", WithEnclaveCtx(mockCtx), WithFs(mockCtx.fs)) - require.NoError(t, err) - - artifact, err := fs.GetArtifact(context.Background(), "test-artifact") - require.NoError(t, err) - - // First extraction - get file1.txt and file3.txt - firstExtractFiles := map[string]string{ - "file1.txt": "content1", - "dir/file3.txt": "content3", - } - - firstWriters := make([]*ArtifactFileWriter, 0, len(firstExtractFiles)) - firstBuffers := make(map[string]*bytes.Buffer, len(firstExtractFiles)) - - for reqPath := range firstExtractFiles { - buf := &bytes.Buffer{} - firstBuffers[reqPath] = buf - firstWriters = append(firstWriters, NewArtifactFileWriter(reqPath, buf)) - } - - // First extraction - err = artifact.ExtractFiles(firstWriters...) - require.NoError(t, err) - - // Verify first extraction - for reqPath, wantContent := range firstExtractFiles { - require.Equal(t, wantContent, firstBuffers[reqPath].String(), - "first extraction: content mismatch for %s", reqPath) - } - - // Second extraction - get file2.txt and file4.txt - secondExtractFiles := map[string]string{ - "file2.txt": "content2", - "dir/file4.txt": "content4", - } - - secondWriters := make([]*ArtifactFileWriter, 0, len(secondExtractFiles)) - secondBuffers := make(map[string]*bytes.Buffer, len(secondExtractFiles)) - - for reqPath := range secondExtractFiles { - buf := &bytes.Buffer{} - secondBuffers[reqPath] = buf - secondWriters = append(secondWriters, NewArtifactFileWriter(reqPath, buf)) - } - - // Second extraction using the same artifact - err = artifact.ExtractFiles(secondWriters...) - require.NoError(t, err) - - // Verify second extraction - for reqPath, wantContent := range secondExtractFiles { - require.Equal(t, wantContent, secondBuffers[reqPath].String(), - "second extraction: content mismatch for %s", reqPath) - } - - // Third extraction - extract all files again to prove we can keep extracting - allFiles := map[string]string{ - "file1.txt": "content1", - "file2.txt": "content2", - "dir/file3.txt": "content3", - "dir/file4.txt": "content4", - } - - allWriters := make([]*ArtifactFileWriter, 0, len(allFiles)) - allBuffers := make(map[string]*bytes.Buffer, len(allFiles)) - - for reqPath := range allFiles { - buf := &bytes.Buffer{} - allBuffers[reqPath] = buf - allWriters = append(allWriters, NewArtifactFileWriter(reqPath, buf)) - } - - // Third extraction - err = artifact.ExtractFiles(allWriters...) - require.NoError(t, err) - - // Verify third extraction - for reqPath, wantContent := range allFiles { - require.Equal(t, wantContent, allBuffers[reqPath].String(), - "third extraction: content mismatch for %s", reqPath) - } -} - -func TestArtifact_Download(t *testing.T) { - tests := []struct { - name string - files map[string][]byte // map of filepath to content - wantErr bool - validate func(t *testing.T, fs afero.Fs) - }{ - { - name: "single file download", - files: map[string][]byte{ - "test.txt": []byte("hello world"), - }, - validate: func(t *testing.T, fs afero.Fs) { - content, err := afero.ReadFile(fs, "test.txt") - require.NoError(t, err) - assert.Equal(t, []byte("hello world"), content) - }, - }, - { - name: "nested directory structure", - files: map[string][]byte{ - "dir/test.txt": []byte("hello"), - "dir/subdir/test.txt": []byte("world"), - }, - validate: func(t *testing.T, fs afero.Fs) { - content1, err := afero.ReadFile(fs, "dir/test.txt") - require.NoError(t, err) - assert.Equal(t, []byte("hello"), content1) - - content2, err := afero.ReadFile(fs, "dir/subdir/test.txt") - require.NoError(t, err) - assert.Equal(t, []byte("world"), content2) - }, - }, - { - name: "empty directory", - files: map[string][]byte{ - "dir/": nil, - }, - validate: func(t *testing.T, fs afero.Fs) { - exists, err := afero.DirExists(fs, "dir") - require.NoError(t, err) - assert.True(t, exists) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create a tar.gz archive in memory - var buf bytes.Buffer - gw := gzip.NewWriter(&buf) - tw := tar.NewWriter(gw) - - // Add files to the archive - for path, content := range tt.files { - header := &tar.Header{ - Name: path, - } - if content == nil { - header.Typeflag = tar.TypeDir - header.Mode = 0755 - } else { - header.Typeflag = tar.TypeReg - header.Size = int64(len(content)) - header.Mode = 0644 - } - - err := tw.WriteHeader(header) - require.NoError(t, err) - - if content != nil { - _, err = tw.Write(content) - require.NoError(t, err) - } - } - - err := tw.Close() - require.NoError(t, err) - err = gw.Close() - require.NoError(t, err) - - // Create in-memory filesystem - memFs := afero.NewMemMapFs() - - // Create an Artifact from the archive - rawData := buf.Bytes() - zipReader, err := gzip.NewReader(bytes.NewReader(rawData)) - require.NoError(t, err) - artifact := &Artifact{ - rawData: rawData, - reader: tar.NewReader(zipReader), - fs: memFs, - } - - // Test Download function - err = artifact.Download("") - if tt.wantErr { - assert.Error(t, err) - return - } - require.NoError(t, err) - - // Run validation - tt.validate(t, memFs) - }) - } -} diff --git a/devnet-sdk/kt/params.go b/devnet-sdk/kt/params.go deleted file mode 100644 index 2125e08e56cfd..0000000000000 --- a/devnet-sdk/kt/params.go +++ /dev/null @@ -1,82 +0,0 @@ -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/devnet-sdk/kt/visitor.go b/devnet-sdk/kt/visitor.go deleted file mode 100644 index e237a6724f7cf..0000000000000 --- a/devnet-sdk/kt/visitor.go +++ /dev/null @@ -1,265 +0,0 @@ -package kt - -import ( - "strconv" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/images" - "github.com/ethereum-optimism/optimism/devnet-sdk/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/devnet-sdk/kt/visitor_test.go b/devnet-sdk/kt/visitor_test.go deleted file mode 100644 index 17ce7bdfb0202..0000000000000 --- a/devnet-sdk/kt/visitor_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package kt - -import ( - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/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/v1.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: "1081032288", - 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: "tag://op-contracts/v1.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/devnet-sdk/manifest/acceptor.go b/devnet-sdk/manifest/acceptor.go deleted file mode 100644 index b7873c6f60714..0000000000000 --- a/devnet-sdk/manifest/acceptor.go +++ /dev/null @@ -1,25 +0,0 @@ -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/devnet-sdk/manifest/manifest.go b/devnet-sdk/manifest/manifest.go deleted file mode 100644 index 65d9e5f7da623..0000000000000 --- a/devnet-sdk/manifest/manifest.go +++ /dev/null @@ -1,104 +0,0 @@ -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/devnet-sdk/manifest/visitor.go b/devnet-sdk/manifest/visitor.go deleted file mode 100644 index c6a3861e33dbc..0000000000000 --- a/devnet-sdk/manifest/visitor.go +++ /dev/null @@ -1,35 +0,0 @@ -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) -} diff --git a/devnet-sdk/proofs/prestate/cmd/main.go b/devnet-sdk/proofs/prestate/cmd/main.go deleted file mode 100644 index 782ac7a642e83..0000000000000 --- a/devnet-sdk/proofs/prestate/cmd/main.go +++ /dev/null @@ -1,99 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "log" - "os" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/proofs/prestate" -) - -type chainConfig struct { - id string - rollupConfig string - genesisConfig string -} - -func parseChainFlag(s string) (*chainConfig, error) { - parts := strings.Split(s, ",") - if len(parts) != 3 { - return nil, fmt.Errorf("chain flag must contain exactly 1 id and 2 files separated by comma") - } - return &chainConfig{ - id: strings.TrimSpace(parts[0]), - rollupConfig: strings.TrimSpace(parts[1]), - genesisConfig: strings.TrimSpace(parts[2]), - }, nil -} - -func main() { - var ( - clientURL = flag.String("url", "http://localhost:8080", "URL of the prestate builder service") - interop = flag.Bool("interop", false, "Generate interop dependency set") - chains = make(chainConfigList, 0) - ) - - flag.Var(&chains, "chain", "Chain configuration files in format: rollup-config.json,genesis-config.json (can be specified multiple times)") - flag.Parse() - - client := prestate.NewPrestateBuilderClient(*clientURL) - ctx := context.Background() - - // Build options list - opts := make([]prestate.PrestateBuilderOption, 0) - - if *interop { - opts = append(opts, prestate.WithGeneratedInteropDepSet()) - } - - // Add chain configs - for i, chain := range chains { - rollupFile, err := os.Open(chain.rollupConfig) - if err != nil { - log.Fatalf("Failed to open rollup config file for chain %d: %v", i, err) - } - defer rollupFile.Close() - - genesisFile, err := os.Open(chain.genesisConfig) - if err != nil { - log.Fatalf("Failed to open genesis config file for chain %d: %v", i, err) - } - defer genesisFile.Close() - - opts = append(opts, prestate.WithChainConfig( - chain.id, - rollupFile, - genesisFile, - )) - } - - // Build prestate - manifest, err := client.BuildPrestate(ctx, opts...) - if err != nil { - log.Fatalf("Failed to build prestate: %v", err) - } - - // Print manifest - for id, hash := range manifest { - fmt.Printf("%s: %s\n", id, hash) - } -} - -// chainConfigList implements flag.Value interface for repeated chain flags -type chainConfigList []*chainConfig - -func (c *chainConfigList) String() string { - return fmt.Sprintf("%v", *c) -} - -func (c *chainConfigList) Set(value string) error { - config, err := parseChainFlag(value) - if err != nil { - return err - } - *c = append(*c, config) - return nil -} diff --git a/devnet-sdk/shell/cmd/ctrl/main.go b/devnet-sdk/shell/cmd/ctrl/main.go deleted file mode 100644 index ed4cdbe30f11c..0000000000000 --- a/devnet-sdk/shell/cmd/ctrl/main.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/ethereum-optimism/optimism/devnet-sdk/controller/surface" - "github.com/ethereum-optimism/optimism/devnet-sdk/shell/env" - "github.com/urfave/cli/v2" -) - -func run(ctx *cli.Context) error { - devnetURL := ctx.String("devnet") - action := ctx.String("action") - service := ctx.String("service") - - devnetEnv, err := env.LoadDevnetFromURL(devnetURL) - if err != nil { - return err - } - - ctrl, err := devnetEnv.Control() - if err != nil { - return err - } - - lc, ok := ctrl.(surface.ServiceLifecycleSurface) - if !ok { - return fmt.Errorf("control surface does not support lifecycle management") - } - - switch action { - case "start": - return lc.StartService(ctx.Context, service) - case "stop": - return lc.StopService(ctx.Context, service) - default: - return fmt.Errorf("invalid action: %s", action) - } -} - -func main() { - app := &cli.App{ - Name: "ctrl", - Usage: "Control devnet services", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "devnet", - Usage: "URL to devnet JSON file", - EnvVars: []string{env.EnvURLVar}, - Required: true, - }, - &cli.StringFlag{ - Name: "action", - Usage: "Action to perform (start or stop)", - Required: true, - Value: "", - Action: func(ctx *cli.Context, v string) error { - if v != "start" && v != "stop" { - return fmt.Errorf("action must be either 'start' or 'stop'") - } - return nil - }, - }, - &cli.StringFlag{ - Name: "service", - Usage: "Service to perform action on", - Required: true, - }, - }, - Action: run, - } - - if err := app.Run(os.Args); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } -} diff --git a/devnet-sdk/shell/cmd/enter/main.go b/devnet-sdk/shell/cmd/enter/main.go deleted file mode 100644 index 5fb98280c4016..0000000000000 --- a/devnet-sdk/shell/cmd/enter/main.go +++ /dev/null @@ -1,93 +0,0 @@ -package main - -import ( - "fmt" - "os" - "os/exec" - - "github.com/ethereum-optimism/optimism/devnet-sdk/shell/env" - "github.com/urfave/cli/v2" -) - -func run(ctx *cli.Context) error { - devnetURL := ctx.String("devnet") - chainName := ctx.String("chain") - nodeIndex := ctx.Int("node-index") - - devnetEnv, err := env.LoadDevnetFromURL(devnetURL) - if err != nil { - return err - } - - chain, err := devnetEnv.GetChain(chainName) - if err != nil { - return err - } - - chainEnv, err := chain.GetEnv( - env.WithCastIntegration(true, nodeIndex), - ) - if err != nil { - return err - } - - if motd := chainEnv.GetMotd(); motd != "" { - fmt.Println(motd) - } - - // Get current environment and append chain-specific vars - env := chainEnv.ApplyToEnv(os.Environ()) - - // Get current shell - shell := os.Getenv("SHELL") - if shell == "" { - shell = "/bin/sh" - } - - // Execute new shell - cmd := exec.Command(shell) - cmd.Env = env - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if err := cmd.Run(); err != nil { - return fmt.Errorf("error executing shell: %w", err) - } - - return nil -} - -func main() { - app := &cli.App{ - Name: "enter", - Usage: "Enter a shell with devnet environment variables set", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "devnet", - Usage: "URL to devnet JSON file", - EnvVars: []string{env.EnvURLVar}, - Required: true, - }, - &cli.StringFlag{ - Name: "chain", - Usage: "Name of the chain to connect to", - EnvVars: []string{env.ChainNameVar}, - Required: true, - }, - &cli.IntFlag{ - Name: "node-index", - Usage: "Index of the node to connect to (default: 0)", - EnvVars: []string{env.NodeIndexVar}, - Required: false, - Value: 0, - }, - }, - Action: run, - } - - if err := app.Run(os.Args); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } -} diff --git a/devnet-sdk/shell/cmd/motd/main.go b/devnet-sdk/shell/cmd/motd/main.go deleted file mode 100644 index c2436ddd6eaa6..0000000000000 --- a/devnet-sdk/shell/cmd/motd/main.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/ethereum-optimism/optimism/devnet-sdk/shell/env" - "github.com/urfave/cli/v2" -) - -func run(ctx *cli.Context) error { - devnetURL := ctx.String("devnet") - chainName := ctx.String("chain") - - devnetEnv, err := env.LoadDevnetFromURL(devnetURL) - if err != nil { - return err - } - - chain, err := devnetEnv.GetChain(chainName) - if err != nil { - return err - } - - chainEnv, err := chain.GetEnv() - if err != nil { - return err - } - - fmt.Println(chainEnv.GetMotd()) - return nil -} - -func main() { - app := &cli.App{ - Name: "motd", - Usage: "Display the Message of the Day for a chain environment", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "devnet", - Usage: "URL to devnet JSON file", - EnvVars: []string{env.EnvURLVar}, - Required: true, - }, - &cli.StringFlag{ - Name: "chain", - Usage: "Name of the chain to get MOTD for", - EnvVars: []string{env.ChainNameVar}, - Required: true, - }, - }, - Action: run, - } - - if err := app.Run(os.Args); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } -} diff --git a/devnet-sdk/shell/env/chain.go b/devnet-sdk/shell/env/chain.go deleted file mode 100644 index 446b66fb6581a..0000000000000 --- a/devnet-sdk/shell/env/chain.go +++ /dev/null @@ -1,187 +0,0 @@ -package env - -import ( - "bytes" - "fmt" - "html/template" - "net/url" - "path/filepath" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" -) - -const ( - EnvURLVar = "DEVNET_ENV_URL" - ChainNameVar = "DEVNET_CHAIN_NAME" - NodeIndexVar = "DEVNET_NODE_INDEX" - ExpectPreconditionsMet = "DEVNET_EXPECT_PRECONDITIONS_MET" - EnvCtrlVar = "DEVNET_ENV_CTRL" -) - -type ChainConfig struct { - chain *descriptors.Chain - devnetURL string - name string -} - -type ChainEnv struct { - motd string - envVars map[string]string -} - -func (c *ChainConfig) getRpcUrl(nodeIndex int) func() (string, error) { - return func() (string, error) { - if len(c.chain.Nodes) == 0 { - return "", fmt.Errorf("chain '%s' has no nodes", c.chain.Name) - } - - if nodeIndex >= len(c.chain.Nodes) { - return "", fmt.Errorf("node index %d is out of bounds for chain '%s'", nodeIndex, c.chain.Name) - } - - // Get RPC endpoint from the first node's execution layer service - elService, ok := c.chain.Nodes[nodeIndex].Services["el"] - if !ok { - return "", fmt.Errorf("no execution layer service found for chain '%s'", c.chain.Name) - } - - rpcEndpoint, ok := elService.Endpoints["rpc"] - if !ok { - return "", fmt.Errorf("no RPC endpoint found for chain '%s'", c.chain.Name) - } - - scheme := rpcEndpoint.Scheme - if scheme == "" { - scheme = "http" - } - return fmt.Sprintf("%s://%s:%d", scheme, rpcEndpoint.Host, rpcEndpoint.Port), nil - } -} - -func (c *ChainConfig) getJwtSecret() (string, error) { - jwt := c.chain.JWT - if len(jwt) >= 2 && jwt[:2] == "0x" { - jwt = jwt[2:] - } - - return jwt, nil -} - -func (c *ChainConfig) motd() string { - tmpl := `You're in a {{.Name}} chain subshell. - - Some addresses of interest: - {{ range $key, $value := .Addresses -}} - {{ printf "%-35s" $key }} = {{ $value }} - {{ end -}} - ` - - t := template.Must(template.New("motd").Parse(tmpl)) - - var buf bytes.Buffer - if err := t.Execute(&buf, c.chain); err != nil { - panic(err) - } - - return buf.String() -} - -type ChainConfigOption func(*ChainConfig, *chainConfigOpts) error - -type chainConfigOpts struct { - extraEnvVars map[string]string -} - -func WithCastIntegration(cast bool, nodeIndex int) ChainConfigOption { - return func(c *ChainConfig, o *chainConfigOpts) error { - mapping := map[string]func() (string, error){ - "ETH_RPC_URL": c.getRpcUrl(nodeIndex), - "ETH_RPC_JWT_SECRET": c.getJwtSecret, - } - - for key, fn := range mapping { - value := "" - var err error - if cast { - if value, err = fn(); err != nil { - return err - } - } - o.extraEnvVars[key] = value - } - return nil - } -} - -func WithExpectedPreconditions(pre bool) ChainConfigOption { - return func(c *ChainConfig, o *chainConfigOpts) error { - if pre { - o.extraEnvVars[ExpectPreconditionsMet] = "true" - } else { - o.extraEnvVars[ExpectPreconditionsMet] = "" - } - return nil - } -} - -func (c *ChainConfig) GetEnv(opts ...ChainConfigOption) (*ChainEnv, error) { - motd := c.motd() - o := &chainConfigOpts{ - extraEnvVars: make(map[string]string), - } - - for _, opt := range opts { - if err := opt(c, o); err != nil { - return nil, err - } - } - - // To allow commands within the shell to know which devnet and chain they are in - absPath := c.devnetURL - if u, err := url.Parse(c.devnetURL); err == nil { - if u.Scheme == "" || u.Scheme == "file" { - // make sure the path is absolute - if abs, err := filepath.Abs(u.Path); err == nil { - absPath = abs - } - } - } - o.extraEnvVars[EnvURLVar] = absPath - o.extraEnvVars[ChainNameVar] = c.name - - return &ChainEnv{ - motd: motd, - envVars: o.extraEnvVars, - }, nil -} - -func (e *ChainEnv) ApplyToEnv(env []string) []string { - // first identify which env vars to clear - clearEnv := make(map[string]interface{}) - for key := range e.envVars { - clearEnv[key] = nil - } - - // then actually remove these from the env - cleanEnv := make([]string, 0) - for _, s := range env { - key := strings.SplitN(s, "=", 2)[0] - if _, ok := clearEnv[key]; !ok { - cleanEnv = append(cleanEnv, s) - } - } - - // then add the remaining env vars - for key, value := range e.envVars { - if value == "" { - continue - } - cleanEnv = append(cleanEnv, fmt.Sprintf("%s=%s", key, value)) - } - return cleanEnv -} - -func (e *ChainEnv) GetMotd() string { - return e.motd -} diff --git a/devnet-sdk/shell/env/devnet.go b/devnet-sdk/shell/env/devnet.go deleted file mode 100644 index 2f1d3baf6eb88..0000000000000 --- a/devnet-sdk/shell/env/devnet.go +++ /dev/null @@ -1,182 +0,0 @@ -package env - -import ( - "fmt" - "math/big" - "net/url" - "os" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/controller/kt" - "github.com/ethereum-optimism/optimism/devnet-sdk/controller/surface" - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum/go-ethereum/params" -) - -type surfaceGetter func() (surface.ControlSurface, error) -type controllerFactory func(*descriptors.DevnetEnvironment) surfaceGetter - -type DevnetEnv struct { - Env *descriptors.DevnetEnvironment - URL string - - ctrl surfaceGetter -} - -// DataFetcher is a function type for fetching data from a URL -type DataFetcher func(*url.URL) (*descriptors.DevnetEnvironment, error) - -type schemeBackend struct { - fetcher DataFetcher - ctrlFactory controllerFactory -} - -func getKurtosisController(env *descriptors.DevnetEnvironment) surfaceGetter { - return func() (surface.ControlSurface, error) { - return kt.NewKurtosisControllerSurface(env) - } -} - -var ( - ktFetcher = &kurtosisFetcher{ - devnetFSFactory: newDevnetFS, - } - - // schemeToBackend maps URL schemes to their respective data fetcher functions - schemeToBackend = map[string]schemeBackend{ - "": {fetchFileData, nil}, - "file": {fetchFileData, nil}, - "kt": {ktFetcher.fetchKurtosisData, getKurtosisController}, - "ktnative": {fetchKurtosisNativeData, getKurtosisController}, - } -) - -// fetchDevnetData retrieves data from a URL based on its scheme -func fetchDevnetData(parsedURL *url.URL) (*descriptors.DevnetEnvironment, error) { - scheme := strings.ToLower(parsedURL.Scheme) - backend, ok := schemeToBackend[scheme] - if !ok { - return nil, fmt.Errorf("unsupported URL scheme: %s", scheme) - } - - return backend.fetcher(parsedURL) -} - -func LoadDevnetFromURL(devnetURL string) (*DevnetEnv, error) { - parsedURL, err := url.Parse(devnetURL) - if err != nil { - return nil, fmt.Errorf("error parsing URL: %w", err) - } - - env, err := fetchDevnetData(parsedURL) - if err != nil { - return nil, fmt.Errorf("error fetching devnet data: %w", err) - } - - if err := fixupDevnetConfig(env); err != nil { - return nil, fmt.Errorf("error fixing up devnet config: %w", err) - } - - var ctrl surfaceGetter - scheme := parsedURL.Scheme - if val, ok := os.LookupEnv(EnvCtrlVar); ok { - scheme = val - } - backend, ok := schemeToBackend[scheme] - if !ok { - return nil, fmt.Errorf("invalid scheme to lookup control interface: %s", scheme) - } - - if backend.ctrlFactory != nil { - ctrl = backend.ctrlFactory(env) - } - - return &DevnetEnv{ - Env: env, - URL: devnetURL, - ctrl: ctrl, - }, nil -} - -func (d *DevnetEnv) GetChain(chainName string) (*ChainConfig, error) { - var chain *descriptors.Chain - if d.Env.L1.Name == chainName { - chain = d.Env.L1 - } else { - for _, l2Chain := range d.Env.L2 { - if l2Chain.Name == chainName { - chain = l2Chain.Chain - break - } - } - } - - if chain == nil { - return nil, fmt.Errorf("chain '%s' not found in devnet config", chainName) - } - - return &ChainConfig{ - chain: chain, - devnetURL: d.URL, - name: chainName, - }, nil -} - -func (d *DevnetEnv) Control() (surface.ControlSurface, error) { - if d.ctrl == nil { - return nil, fmt.Errorf("devnet is not controllable") - } - return d.ctrl() -} - -func fixupDevnetConfig(config *descriptors.DevnetEnvironment) error { - // we should really get this from the kurtosis output, but the data doesn't exist yet, so craft a minimal one. - l1ID := new(big.Int) - l1ID, ok := l1ID.SetString(config.L1.ID, 10) - if !ok { - return fmt.Errorf("invalid L1 ID: %s", config.L1.ID) - } - if config.L1.Config == nil { - if l1Config := eth.L1ChainConfigByChainID(eth.ChainIDFromBig(l1ID)); l1Config != nil { - config.L1.Config = l1Config - } else { - config.L1.Config = ¶ms.ChainConfig{ - ChainID: l1ID, - } - } - } - for _, l2Chain := range config.L2 { - l2ChainId := l2Chain.Chain.ID - - var l2ID *big.Int - base := 10 - if len(l2ChainId) >= 2 && l2ChainId[:2] == "0x" { - base = 16 - l2ChainId = l2ChainId[2:] - } - - l2ID, ok := new(big.Int).SetString(l2ChainId, base) - if !ok { - return fmt.Errorf("invalid L2 ID: %s", l2ChainId) - } - // Convert the L2 chain ID to decimal string format - decimalId := l2ID.String() - l2Chain.Chain.ID = decimalId - - if l2Chain.Config == nil { - l2Chain.Config = ¶ms.ChainConfig{ - ChainID: l2ID, - } - } - - if l2Chain.RollupConfig == nil { - l2Chain.RollupConfig = &rollup.Config{ - L1ChainID: l1ID, - L2ChainID: l2ID, - } - } - } - return nil -} diff --git a/devnet-sdk/shell/env/env_test.go b/devnet-sdk/shell/env/env_test.go deleted file mode 100644 index d2b7aaf44a766..0000000000000 --- a/devnet-sdk/shell/env/env_test.go +++ /dev/null @@ -1,309 +0,0 @@ -package env - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestLoadDevnetEnv(t *testing.T) { - // Create a temporary test file - content := `{ - "l1": { - "name": "l1", - "id": "1", - "nodes": [{ - "services": { - "el": { - "endpoints": { - "rpc": { - "host": "localhost", - "port": 8545 - } - } - } - } - }], - "jwt": "0x1234567890abcdef", - "addresses": { - "deployer": "0x1234567890123456789012345678901234567890" - } - }, - "l2": [{ - "name": "op", - "id": "2", - "nodes": [{ - "services": { - "el": { - "endpoints": { - "rpc": { - "host": "localhost", - "port": 9545 - } - } - } - } - }], - "jwt": "0xdeadbeef", - "addresses": { - "deployer": "0x2345678901234567890123456789012345678901" - } - }] - }` - - tmpfile, err := os.CreateTemp("", "devnet-*.json") - require.NoError(t, err) - defer os.Remove(tmpfile.Name()) - - _, err = tmpfile.Write([]byte(content)) - require.NoError(t, err) - err = tmpfile.Close() - require.NoError(t, err) - - // Test successful load - t.Run("successful load", func(t *testing.T) { - env, err := LoadDevnetFromURL(tmpfile.Name()) - require.NoError(t, err) - assert.Equal(t, "l1", env.Env.L1.Name) - assert.Equal(t, "op", env.Env.L2[0].Name) - }) - - // Test loading non-existent file - t.Run("non-existent file", func(t *testing.T) { - _, err := LoadDevnetFromURL("non-existent.json") - assert.Error(t, err) - }) - - // Test loading invalid JSON - t.Run("invalid JSON", func(t *testing.T) { - invalidFile := filepath.Join(t.TempDir(), "invalid.json") - err := os.WriteFile(invalidFile, []byte("{invalid json}"), 0644) - require.NoError(t, err) - - _, err = LoadDevnetFromURL(invalidFile) - assert.Error(t, err) - }) -} - -func TestGetChain(t *testing.T) { - devnet := &DevnetEnv{ - Env: &descriptors.DevnetEnvironment{ - L1: &descriptors.Chain{ - Name: "l1", - Nodes: []descriptors.Node{ - { - Services: descriptors.ServiceMap{ - "el": { - Endpoints: descriptors.EndpointMap{ - "rpc": { - Host: "localhost", - Port: 8545, - }, - }, - }, - }, - }, - }, - JWT: "0x1234", - Addresses: descriptors.AddressMap{ - "deployer": common.HexToAddress("0x1234567890123456789012345678901234567890"), - }, - }, - L2: []*descriptors.L2Chain{ - { - Chain: &descriptors.Chain{ - Name: "op", - Nodes: []descriptors.Node{ - { - Services: descriptors.ServiceMap{ - "el": { - Endpoints: descriptors.EndpointMap{ - "rpc": { - Host: "localhost", - Port: 9545, - }, - }, - }, - }, - }, - }, - JWT: "0x5678", - Addresses: descriptors.AddressMap{ - "deployer": common.HexToAddress("0x2345678901234567890123456789012345678901"), - }, - }, - L1Wallets: descriptors.WalletMap{ - "deployer": &descriptors.Wallet{ - Address: common.HexToAddress("0x2345678901234567890123456789012345678901"), - PrivateKey: "0x2345678901234567890123456789012345678901", - }, - }, - }, - }, - }, - URL: "test.json", - } - - // Test getting L1 chain - t.Run("get L1 chain", func(t *testing.T) { - chain, err := devnet.GetChain("l1") - require.NoError(t, err) - assert.Equal(t, "l1", chain.name) - assert.Equal(t, "0x1234", chain.chain.JWT) - }) - - // Test getting L2 chain - t.Run("get L2 chain", func(t *testing.T) { - chain, err := devnet.GetChain("op") - require.NoError(t, err) - assert.Equal(t, "op", chain.name) - assert.Equal(t, "0x5678", chain.chain.JWT) - }) - - // Test getting non-existent chain - t.Run("get non-existent chain", func(t *testing.T) { - _, err := devnet.GetChain("invalid") - assert.Error(t, err) - }) -} - -func TestChainConfig(t *testing.T) { - chain := &ChainConfig{ - chain: &descriptors.Chain{ - Name: "test", - Nodes: []descriptors.Node{ - { - Services: descriptors.ServiceMap{ - "el": { - Endpoints: descriptors.EndpointMap{ - "rpc": { - Host: "localhost", - Port: 8545, - Scheme: "https", - }, - }, - }, - }, - }, - }, - JWT: "0x1234", - Addresses: descriptors.AddressMap{ - "deployer": common.HexToAddress("0x1234567890123456789012345678901234567890"), - }, - }, - devnetURL: "test.json", - name: "test", - } - - // Test getting environment variables - t.Run("get environment variables", func(t *testing.T) { - env, err := chain.GetEnv( - WithCastIntegration(true, 0), - ) - require.NoError(t, err) - - assert.Equal(t, "https://localhost:8545", env.envVars["ETH_RPC_URL"]) - assert.Equal(t, "1234", env.envVars["ETH_RPC_JWT_SECRET"]) - assert.Equal(t, "test.json", filepath.Base(env.envVars[EnvURLVar])) - assert.Equal(t, "test", env.envVars[ChainNameVar]) - assert.Contains(t, env.motd, "deployer") - assert.Contains(t, env.motd, "0x1234567890123456789012345678901234567890") - }) - - // Test chain with no nodes - t.Run("chain with no nodes", func(t *testing.T) { - noNodesChain := &ChainConfig{ - chain: &descriptors.Chain{ - Name: "test", - Nodes: []descriptors.Node{}, - }, - } - _, err := noNodesChain.GetEnv( - WithCastIntegration(true, 0), - ) - assert.Error(t, err) - }) - - // Test chain with missing service - t.Run("chain with missing service", func(t *testing.T) { - missingServiceChain := &ChainConfig{ - chain: &descriptors.Chain{ - Name: "test", - Nodes: []descriptors.Node{ - { - Services: descriptors.ServiceMap{}, - }, - }, - }, - } - _, err := missingServiceChain.GetEnv( - WithCastIntegration(true, 0), - ) - assert.Error(t, err) - }) - - // Test chain with missing endpoint - t.Run("chain with missing endpoint", func(t *testing.T) { - missingEndpointChain := &ChainConfig{ - chain: &descriptors.Chain{ - Name: "test", - Nodes: []descriptors.Node{ - { - Services: descriptors.ServiceMap{ - "el": { - Endpoints: descriptors.EndpointMap{}, - }, - }, - }, - }, - }, - } - _, err := missingEndpointChain.GetEnv( - WithCastIntegration(true, 0), - ) - assert.Error(t, err) - }) -} - -func TestChainEnv_ApplyToEnv(t *testing.T) { - originalEnv := []string{ - "KEEP_ME=old_value", - "OVERRIDE_ME=old_value", - "REMOVE_ME=old_value", - } - - env := &ChainEnv{ - envVars: map[string]string{ - "OVERRIDE_ME": "new_value", - "REMOVE_ME": "", - }, - } - - result := env.ApplyToEnv(originalEnv) - - // Convert result to map for easier testing - resultMap := make(map[string]string) - for _, v := range result { - parts := strings.SplitN(v, "=", 2) - resultMap[parts[0]] = parts[1] - } - - // Test that KEEP_ME was overridden with new value - assert.Equal(t, "old_value", resultMap["KEEP_ME"]) - - // Test that OVERRIDE_ME was overridden with new value - assert.Equal(t, "new_value", resultMap["OVERRIDE_ME"]) - - // Test that REMOVE_ME was removed (not present in result) - _, exists := resultMap["REMOVE_ME"] - assert.False(t, exists, "REMOVE_ME should have been removed") - - // Test that we have exactly 3 variables in the result - assert.Equal(t, 2, len(result), "Result should have exactly 3 variables") -} diff --git a/devnet-sdk/shell/env/file_fetch.go b/devnet-sdk/shell/env/file_fetch.go deleted file mode 100644 index 929331d26659c..0000000000000 --- a/devnet-sdk/shell/env/file_fetch.go +++ /dev/null @@ -1,49 +0,0 @@ -package env - -import ( - "encoding/json" - "fmt" - "net/url" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/spf13/afero" -) - -type fileFetcher struct { - fs afero.Fs -} - -// fetchFileData reads data from a local file -func (f *fileFetcher) fetchFileData(u *url.URL) (*descriptors.DevnetEnvironment, error) { - body, err := afero.ReadFile(f.fs, u.Path) - if err != nil { - return nil, fmt.Errorf("error reading file: %w", err) - } - - basename := u.Path - if lastSlash := strings.LastIndex(basename, "/"); lastSlash >= 0 { - basename = basename[lastSlash+1:] - } - if lastDot := strings.LastIndex(basename, "."); lastDot >= 0 { - basename = basename[:lastDot] - } - - var config descriptors.DevnetEnvironment - if err := json.Unmarshal(body, &config); err != nil { - return nil, fmt.Errorf("error parsing JSON: %w", err) - } - - // If the name is not set, use the basename of the file - if config.Name == "" { - config.Name = basename - } - return &config, nil -} - -func fetchFileData(u *url.URL) (*descriptors.DevnetEnvironment, error) { - fetcher := &fileFetcher{ - fs: afero.NewOsFs(), - } - return fetcher.fetchFileData(u) -} diff --git a/devnet-sdk/shell/env/file_fetch_test.go b/devnet-sdk/shell/env/file_fetch_test.go deleted file mode 100644 index b6e722e0a6684..0000000000000 --- a/devnet-sdk/shell/env/file_fetch_test.go +++ /dev/null @@ -1,93 +0,0 @@ -package env - -import ( - "net/url" - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestFetchFileDataFromOS(t *testing.T) { - fs := afero.NewMemMapFs() - - var ( - absoluteContent = []byte(`{"name": "absolute"}`) - relativeContent = []byte(`{"name": "relative"}`) - noNameContent = []byte(`{}`) - ) - - err := afero.WriteFile(fs, "/some/absolute/path", absoluteContent, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "some/relative/path", relativeContent, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "some/file/devnet-env.json", noNameContent, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "some/file/devnet", noNameContent, 0644) - require.NoError(t, err) - - fetcher := &fileFetcher{ - fs: fs, - } - - tests := []struct { - name string - urlStr string - wantName string - wantContent []byte - wantError bool - }{ - { - name: "file URL", - urlStr: "file:///some/absolute/path", - wantName: "absolute", - wantContent: absoluteContent, - }, - { - name: "absolute path", - urlStr: "/some/absolute/path", - wantName: "absolute", - wantContent: absoluteContent, - }, - { - name: "relative path", - urlStr: "some/relative/path", - wantName: "relative", - wantContent: relativeContent, - }, - { - name: "no name - file with extension", - urlStr: "some/file/devnet-env.json", - wantName: "devnet-env", - wantContent: noNameContent, - }, - { - name: "no name - file without extension", - urlStr: "some/file/devnet", - wantName: "devnet", - wantContent: noNameContent, - }, - { - name: "non-existent file", - urlStr: "file:///nonexistent/path", - wantError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - u, err := url.Parse(tt.urlStr) - require.NoError(t, err) - - env, err := fetcher.fetchFileData(u) - if tt.wantError { - assert.Error(t, err) - return - } - - require.NoError(t, err) - assert.Equal(t, tt.wantName, env.Name) - }) - } -} diff --git a/devnet-sdk/shell/env/kt_fetch.go b/devnet-sdk/shell/env/kt_fetch.go deleted file mode 100644 index 2ac5553919d42..0000000000000 --- a/devnet-sdk/shell/env/kt_fetch.go +++ /dev/null @@ -1,69 +0,0 @@ -package env - -import ( - "context" - "fmt" - "net/url" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" -) - -// DevnetFS is an interface that both our mock and the real implementation satisfy -type DevnetFS interface { - GetDevnetDescriptor(ctx context.Context, opts ...ktfs.DevnetFSDescriptorOption) (*descriptors.DevnetEnvironment, error) -} - -type devnetFSFactory func(ctx context.Context, enclave string) (DevnetFS, error) - -type kurtosisFetcher struct { - devnetFSFactory devnetFSFactory -} - -func newDevnetFS(ctx context.Context, enclave string) (DevnetFS, error) { - fs, err := ktfs.NewEnclaveFS(ctx, enclave) - if err != nil { - return nil, err - } - return ktfs.NewDevnetFS(fs), nil -} - -// parseKurtosisURL parses a Kurtosis URL of the form kt://enclave/artifact/file -// If artifact is omitted, it defaults to "" -// If file is omitted, it defaults to "env.json" -func (f *kurtosisFetcher) parseKurtosisURL(u *url.URL) (enclave, artifactName, fileName string) { - enclave = u.Host - artifactName = "" - fileName = ktfs.DevnetEnvArtifactPath - - // Trim both prefix and suffix slashes before splitting - trimmedPath := strings.Trim(u.Path, "/") - parts := strings.Split(trimmedPath, "/") - if len(parts) > 0 && parts[0] != "" { - artifactName = parts[0] - } - if len(parts) > 1 && parts[1] != "" { - fileName = parts[1] - } - - return -} - -// fetchKurtosisData reads data from a Kurtosis artifact -func (f *kurtosisFetcher) fetchKurtosisData(u *url.URL) (*descriptors.DevnetEnvironment, error) { - enclave, artifactName, fileName := f.parseKurtosisURL(u) - - devnetFS, err := f.devnetFSFactory(context.Background(), enclave) - if err != nil { - return nil, fmt.Errorf("error creating enclave fs: %w", err) - } - - env, err := devnetFS.GetDevnetDescriptor(context.Background(), ktfs.WithArtifactName(artifactName), ktfs.WithArtifactPath(fileName)) - if err != nil { - return nil, fmt.Errorf("error getting devnet descriptor: %w", err) - } - - env.Name = enclave - return env, nil -} diff --git a/devnet-sdk/shell/env/kt_fetch_test.go b/devnet-sdk/shell/env/kt_fetch_test.go deleted file mode 100644 index 46da0e091671d..0000000000000 --- a/devnet-sdk/shell/env/kt_fetch_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package env - -import ( - "net/url" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestParseKurtosisURL(t *testing.T) { - tests := []struct { - name string - urlStr string - wantEnclave string - wantArtifact string - wantFile string - wantParseError bool - }{ - { - name: "basic url", - urlStr: "kt://myenclave", - wantEnclave: "myenclave", - wantArtifact: "", - wantFile: "env.json", - }, - { - name: "with artifact", - urlStr: "kt://myenclave/custom-artifact", - wantEnclave: "myenclave", - wantArtifact: "custom-artifact", - wantFile: "env.json", - }, - { - name: "with artifact and file", - urlStr: "kt://myenclave/custom-artifact/config.json", - wantEnclave: "myenclave", - wantArtifact: "custom-artifact", - wantFile: "config.json", - }, - { - name: "with trailing slash", - urlStr: "kt://enclave/artifact/", - wantEnclave: "enclave", - wantArtifact: "artifact", - wantFile: "env.json", - }, - { - name: "invalid url", - urlStr: "://invalid", - wantParseError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - u, err := url.Parse(tt.urlStr) - if tt.wantParseError { - assert.Error(t, err) - return - } - require.NoError(t, err) - - enclave, artifact, file := ktFetcher.parseKurtosisURL(u) - assert.Equal(t, tt.wantEnclave, enclave) - assert.Equal(t, tt.wantArtifact, artifact) - assert.Equal(t, tt.wantFile, file) - }) - } -} diff --git a/devnet-sdk/shell/env/kt_native_fetch.go b/devnet-sdk/shell/env/kt_native_fetch.go deleted file mode 100644 index 3693fa887b569..0000000000000 --- a/devnet-sdk/shell/env/kt_native_fetch.go +++ /dev/null @@ -1,119 +0,0 @@ -package env - -import ( - "context" - "fmt" - "io" - "net/url" - "os" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" -) - -// parseKurtosisNativeURL parses a Kurtosis URL of the form kt://enclave/artifact/file -// If artifact is omitted, it defaults to "devnet" -// If file is omitted, it defaults to "env.json" -func parseKurtosisNativeURL(u *url.URL) (enclave, argsFileName string) { - enclave = u.Host - argsFileName = "/" + strings.Trim(u.Path, "/") - - return -} - -// fetchKurtosisNativeData reads data directly from kurtosis API using default dependency implementations -func fetchKurtosisNativeData(u *url.URL) (*descriptors.DevnetEnvironment, error) { - return fetchKurtosisNativeDataInternal(u, &defaultOSOpenImpl{}, &defaultSpecImpl{}, &defaultKurtosisImpl{}) -} - -// fetchKurtosisNativeDataInternal reads data directly from kurtosis API using provided dependency implementations -func fetchKurtosisNativeDataInternal(u *url.URL, osImpl osOpenInterface, specImpl specInterface, kurtosisImpl kurtosisInterface) (*descriptors.DevnetEnvironment, error) { - // First let's parse the kurtosis URL - enclave, argsFileName := parseKurtosisNativeURL(u) - - // Open the arguments file - argsFile, err := osImpl.Open(argsFileName) - if err != nil { - return nil, fmt.Errorf("error reading arguments file: %w", err) - } - - // Make sure to close the file once we're done reading - defer argsFile.Close() - - // Once we have the arguments file, we can extract the enclave spec - enclaveSpec, err := specImpl.ExtractData(argsFile) - if err != nil { - return nil, fmt.Errorf("error extracting enclave spec: %w", err) - } - - // We'll use the deployer to extract the system spec - deployer, err := kurtosisImpl.NewKurtosisDeployer(kurtosis.WithKurtosisEnclave(enclave)) - if err != nil { - return nil, fmt.Errorf("error creating deployer: %w", err) - } - - // We'll read the environment info from kurtosis directly - ctx := context.Background() - env, err := deployer.GetEnvironmentInfo(ctx, enclaveSpec) - if err != nil { - return nil, fmt.Errorf("error getting environment info: %w", err) - } - - return env.DevnetEnvironment, nil -} - -// osOpenInterface describes a struct that can open filesystem files for reading -// -// osOpenInterface is used when loading kurtosis args files from local filesystem -type osOpenInterface interface { - Open(name string) (fileInterface, error) -} - -// fileInterface describes a subset of os.File struct for ease of testing -type fileInterface interface { - io.Reader - Close() error -} - -// defaultOSOpenImpl implements osOpenInterface -type defaultOSOpenImpl struct{} - -func (d *defaultOSOpenImpl) Open(name string) (fileInterface, error) { - return os.Open(name) -} - -// specInterface describes a subset of functionality required from the spec package -// -// The spec package is responsible for turning a kurtosis args file into an EnclaveSpec -type specInterface interface { - ExtractData(r io.Reader) (*spec.EnclaveSpec, error) -} - -// defaultSpecImpl implements specInterface -type defaultSpecImpl struct{} - -func (d *defaultSpecImpl) ExtractData(r io.Reader) (*spec.EnclaveSpec, error) { - return spec.NewSpec().ExtractData(r) -} - -// kurtosisInterface describes a subset of functionality required from the kurtosis package -// -// kurtosisInterface provides access to a KurtosisDeployer object, an intermediate object that provides -// access to the KurtosisEnvironment object -type kurtosisInterface interface { - NewKurtosisDeployer(opts ...kurtosis.KurtosisDeployerOptions) (kurtosisDeployerInterface, error) -} - -// kurtosisDeployerInterface describes a subset of functionality required from KurtosisDeployer struct -type kurtosisDeployerInterface interface { - GetEnvironmentInfo(ctx context.Context, spec *spec.EnclaveSpec) (*kurtosis.KurtosisEnvironment, error) -} - -// defaultKurtosisImpl implements kurtosisInterface -type defaultKurtosisImpl struct{} - -func (d *defaultKurtosisImpl) NewKurtosisDeployer(opts ...kurtosis.KurtosisDeployerOptions) (kurtosisDeployerInterface, error) { - return kurtosis.NewKurtosisDeployer(opts...) -} diff --git a/devnet-sdk/shell/env/kt_native_fetch_test.go b/devnet-sdk/shell/env/kt_native_fetch_test.go deleted file mode 100644 index 8c23cfb28d4be..0000000000000 --- a/devnet-sdk/shell/env/kt_native_fetch_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package env - -import ( - "context" - "embed" - "encoding/json" - "fmt" - "io" - "net/url" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var ( - //go:embed testdata/kurtosis - kurtosisTestData embed.FS -) - -func TestParseKurtosisNativeURL(t *testing.T) { - tests := []struct { - name string - urlStr string - wantEnclave string - wantArtifact string - wantFile string - wantParseError bool - }{ - { - name: "absolute file path", - urlStr: "ktnative://myenclave/path/args.yaml", - wantEnclave: "myenclave", - wantFile: "/path/args.yaml", - }, - { - name: "invalid url", - urlStr: "://invalid", - wantParseError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - u, err := url.Parse(tt.urlStr) - if tt.wantParseError { - assert.Error(t, err) - return - } - require.NoError(t, err) - - enclave, argsFile := parseKurtosisNativeURL(u) - assert.Equal(t, tt.wantEnclave, enclave) - assert.Equal(t, tt.wantFile, argsFile) - }) - } -} - -func TestFetchKurtosisNativeDataFailures(t *testing.T) { - url, err := url.Parse("ktnative://enclave/file/path") - require.NoError(t, err) - - t.Run("non-existent args file", func(t *testing.T) { - osImpl := &mockOSImpl{ - err: fmt.Errorf("oh no"), - } - - _, err = fetchKurtosisNativeDataInternal(url, osImpl, &defaultSpecImpl{}, &defaultKurtosisImpl{}) - require.ErrorContains(t, err, "error reading arguments file: oh no") - }) - - t.Run("malformed args file", func(t *testing.T) { - file, err := kurtosisTestData.Open("testdata/kurtosis/args--malformed.txt") - require.NoError(t, err) - - osImpl := &mockOSImpl{ - value: file, - } - - _, err = fetchKurtosisNativeDataInternal(url, osImpl, &defaultSpecImpl{}, &defaultKurtosisImpl{}) - require.ErrorContains(t, err, "error extracting enclave spec: failed to decode YAML: yaml: unmarshal errors:") - }) - - t.Run("spec extraction failure", func(t *testing.T) { - file, err := kurtosisTestData.Open("testdata/kurtosis/args--simple.yaml") - require.NoError(t, err) - - osImpl := &mockOSImpl{ - value: file, - } - - specImpl := &mockSpecImpl{ - err: fmt.Errorf("oh no"), - } - - _, err = fetchKurtosisNativeDataInternal(url, osImpl, specImpl, &defaultKurtosisImpl{}) - require.ErrorContains(t, err, "error extracting enclave spec: oh no") - }) - - t.Run("kurtosis deployer failure", func(t *testing.T) { - file, err := kurtosisTestData.Open("testdata/kurtosis/args--simple.yaml") - require.NoError(t, err) - - osImpl := &mockOSImpl{ - value: file, - } - - kurtosisImpl := &mockKurtosisImpl{ - err: fmt.Errorf("oh no"), - } - - _, err = fetchKurtosisNativeDataInternal(url, osImpl, &defaultSpecImpl{}, kurtosisImpl) - require.ErrorContains(t, err, "error creating deployer: oh no") - }) - - t.Run("kurtosis info extraction failure", func(t *testing.T) { - file, err := kurtosisTestData.Open("testdata/kurtosis/args--simple.yaml") - require.NoError(t, err) - - osImpl := &mockOSImpl{ - value: file, - } - - kurtosisDeployer := &mockKurtosisDeployerImpl{ - err: fmt.Errorf("oh no"), - } - - kurtosisImpl := &mockKurtosisImpl{ - value: kurtosisDeployer, - } - - _, err = fetchKurtosisNativeDataInternal(url, osImpl, &defaultSpecImpl{}, kurtosisImpl) - require.ErrorContains(t, err, "error getting environment info: oh no") - }) -} - -func TestFetchKurtosisNativeDataSuccess(t *testing.T) { - url, err := url.Parse("ktnative://enclave/file/path") - require.NoError(t, err) - - t.Run("fetching success", func(t *testing.T) { - file, err := kurtosisTestData.Open("testdata/kurtosis/args--simple.yaml") - require.NoError(t, err) - - // We'll prepare a mock spec to be returned - envSpec := &spec.EnclaveSpec{} - env := &kurtosis.KurtosisEnvironment{ - DevnetEnvironment: &descriptors.DevnetEnvironment{ - Name: "enclave", - L2: make([]*descriptors.L2Chain, 0, 1), - Features: envSpec.Features, - }, - } - - // And serialize it so that we can compare values - _, err = json.MarshalIndent(env, "", " ") - require.NoError(t, err) - - osImpl := &mockOSImpl{ - value: file, - } - - specImpl := &mockSpecImpl{ - value: envSpec, - } - - kurtosisDeployer := &mockKurtosisDeployerImpl{ - value: env, - } - - kurtosisImpl := &mockKurtosisImpl{ - value: kurtosisDeployer, - } - - devnetEnv, err := fetchKurtosisNativeDataInternal(url, osImpl, specImpl, kurtosisImpl) - require.NoError(t, err) - require.Equal(t, "enclave", devnetEnv.Name) - }) -} - -var ( - _ osOpenInterface = (*mockOSImpl)(nil) - - _ specInterface = (*mockSpecImpl)(nil) - - _ kurtosisInterface = (*mockKurtosisImpl)(nil) - - _ kurtosisDeployerInterface = (*mockKurtosisDeployerImpl)(nil) -) - -type mockOSImpl struct { - value fileInterface - err error -} - -func (o *mockOSImpl) Open(name string) (fileInterface, error) { - return o.value, o.err -} - -type mockSpecImpl struct { - value *spec.EnclaveSpec - err error -} - -func (o *mockSpecImpl) ExtractData(r io.Reader) (*spec.EnclaveSpec, error) { - return o.value, o.err -} - -type mockKurtosisImpl struct { - value kurtosisDeployerInterface - err error -} - -func (o *mockKurtosisImpl) NewKurtosisDeployer(opts ...kurtosis.KurtosisDeployerOptions) (kurtosisDeployerInterface, error) { - return o.value, o.err -} - -type mockKurtosisDeployerImpl struct { - value *kurtosis.KurtosisEnvironment - err error -} - -func (o *mockKurtosisDeployerImpl) GetEnvironmentInfo(context.Context, *spec.EnclaveSpec) (*kurtosis.KurtosisEnvironment, error) { - return o.value, o.err -} diff --git a/devnet-sdk/shell/env/testdata/kurtosis/args--malformed.txt b/devnet-sdk/shell/env/testdata/kurtosis/args--malformed.txt deleted file mode 100644 index 7b1f8d92fc758..0000000000000 --- a/devnet-sdk/shell/env/testdata/kurtosis/args--malformed.txt +++ /dev/null @@ -1 +0,0 @@ -what is this \ No newline at end of file diff --git a/devnet-sdk/shell/env/testdata/kurtosis/args--simple.yaml b/devnet-sdk/shell/env/testdata/kurtosis/args--simple.yaml deleted file mode 100644 index 28644893b554c..0000000000000 --- a/devnet-sdk/shell/env/testdata/kurtosis/args--simple.yaml +++ /dev/null @@ -1,6 +0,0 @@ -optimism_package: - chains: - op-kurtosis: - whatakey: - - el_type: op-geth - cl_type: op-node \ No newline at end of file diff --git a/devnet-sdk/system/chain.go b/devnet-sdk/system/chain.go deleted file mode 100644 index 20f82796f95a0..0000000000000 --- a/devnet-sdk/system/chain.go +++ /dev/null @@ -1,259 +0,0 @@ -package system - -import ( - "context" - "fmt" - "math/big" - "sync" - "time" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum-optimism/optimism/op-service/client" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/sources" - "github.com/ethereum/go-ethereum/common" - coreTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rpc" -) - -// this is to differentiate between op-geth and go-ethereum -type opBlock interface { - WithdrawalsRoot() *common.Hash -} - -var ( - // This will make sure that we implement the Chain interface - _ Chain = (*chain)(nil) - _ L2Chain = (*l2Chain)(nil) - - // Make sure we're using op-geth in place of go-ethereum. - // If you're wondering why this fails at compile time, - // it's most likely because you're not using a "replace" - // directive in your go.mod file. - _ opBlock = (*coreTypes.Block)(nil) -) - -// clientManager handles ethclient connections -type clientManager struct { - mu sync.RWMutex - clients map[string]*sources.EthClient - gethClients map[string]*ethclient.Client -} - -func newClientManager() *clientManager { - return &clientManager{ - clients: make(map[string]*sources.EthClient), - gethClients: make(map[string]*ethclient.Client), - } -} - -func (m *clientManager) Client(rpcURL string) (*sources.EthClient, error) { - m.mu.RLock() - if client, ok := m.clients[rpcURL]; ok { - m.mu.RUnlock() - return client, nil - } - m.mu.RUnlock() - - m.mu.Lock() - defer m.mu.Unlock() - - // Double-check after acquiring write lock - if client, ok := m.clients[rpcURL]; ok { - return client, nil - } - - ethClCfg := sources.EthClientConfig{ - MaxRequestsPerBatch: 10, - MaxConcurrentRequests: 10, - ReceiptsCacheSize: 10, - TransactionsCacheSize: 10, - HeadersCacheSize: 10, - PayloadsCacheSize: 10, - BlockRefsCacheSize: 10, - TrustRPC: false, - MustBePostMerge: true, - RPCProviderKind: sources.RPCKindStandard, - MethodResetDuration: time.Minute, - } - rpcClient, err := rpc.DialContext(context.Background(), rpcURL) - if err != nil { - return nil, err - } - ethCl, err := sources.NewEthClient(client.NewBaseRPCClient(rpcClient), log.Root(), nil, ðClCfg) - if err != nil { - return nil, err - } - m.clients[rpcURL] = ethCl - return ethCl, nil -} - -func (m *clientManager) GethClient(rpcURL string) (*ethclient.Client, error) { - m.mu.RLock() - if client, ok := m.gethClients[rpcURL]; ok { - m.mu.RUnlock() - return client, nil - } - m.mu.RUnlock() - - m.mu.Lock() - defer m.mu.Unlock() - - // Double-check after acquiring write lock - if client, ok := m.gethClients[rpcURL]; ok { - return client, nil - } - - client, err := ethclient.Dial(rpcURL) - if err != nil { - return nil, err - } - m.gethClients[rpcURL] = client - return client, nil -} - -type chain struct { - id string - wallets WalletMap - nodes []Node - chainConfig *params.ChainConfig - addresses AddressMap -} - -func (c *chain) Nodes() []Node { - return c.nodes -} - -// Wallet returns the first wallet which meets all provided constraints, or an -// error. -// Typically this will be one of the pre-funded wallets associated with -// the deployed system. -func (c *chain) Wallets() WalletMap { - return c.wallets -} - -func (c *chain) ID() types.ChainID { - if c.id == "" { - return types.ChainID(big.NewInt(0)) - } - base := 10 - if len(c.id) >= 2 && c.id[0:2] == "0x" { - c.id = c.id[2:] - base = 16 - } - id, ok := new(big.Int).SetString(c.id, base) - if !ok { - return types.ChainID(big.NewInt(0)) - } - return types.ChainID(id) -} - -func (c *chain) Config() (*params.ChainConfig, error) { - if c.chainConfig == nil { - return nil, fmt.Errorf("chain config is nil") - } - return c.chainConfig, nil -} - -func (c *chain) Addresses() AddressMap { - return c.addresses -} - -// SupportsEIP checks if the chain's first node supports the given EIP -func (c *chain) SupportsEIP(ctx context.Context, eip uint64) bool { - if len(c.nodes) == 0 { - return false - } - return c.nodes[0].SupportsEIP(ctx, eip) -} - -func checkHeader(ctx context.Context, client *sources.EthClient, check func(eth.BlockInfo) bool) bool { - info, err := client.InfoByLabel(ctx, eth.Unsafe) - if err != nil { - return false - } - return check(info) -} - -func newNodesFromDescriptor(d *descriptors.Chain) []Node { - clients := newClientManager() - nodes := make([]Node, len(d.Nodes)) - for i, node := range d.Nodes { - svc := node.Services["el"] - name := svc.Name - rpc := svc.Endpoints["rpc"] - if rpc.Scheme == "" { - rpc.Scheme = "http" - } - nodes[i] = newNode(fmt.Sprintf("%s://%s:%d", rpc.Scheme, rpc.Host, rpc.Port), name, clients) - } - return nodes -} - -func newChainFromDescriptor(d *descriptors.Chain) (*chain, error) { - // TODO: handle incorrect descriptors better. We could panic here. - - nodes := newNodesFromDescriptor(d) - c := newChain(d.ID, nil, d.Config, AddressMap(d.Addresses), nodes) // Create chain first - - wallets, err := newWalletMapFromDescriptorWalletMap(d.Wallets, c) - if err != nil { - return nil, err - } - c.wallets = wallets - - return c, nil -} - -func newChain(chainID string, wallets WalletMap, chainConfig *params.ChainConfig, addresses AddressMap, nodes []Node) *chain { - chain := &chain{ - id: chainID, - wallets: wallets, - nodes: nodes, - chainConfig: chainConfig, - addresses: addresses, - } - return chain -} - -func newL2ChainFromDescriptor(d *descriptors.L2Chain) (*l2Chain, error) { - // TODO: handle incorrect descriptors better. We could panic here. - - nodes := newNodesFromDescriptor(d.Chain) - c := newL2Chain(d.ID, nil, nil, d.Config, AddressMap(d.Addresses), nodes) // Create chain first - - l2Wallets, err := newWalletMapFromDescriptorWalletMap(d.Wallets, c) - if err != nil { - return nil, err - } - c.wallets = l2Wallets - - l1Wallets, err := newWalletMapFromDescriptorWalletMap(d.L1Wallets, c) - if err != nil { - return nil, err - } - c.l1Wallets = l1Wallets - - return c, nil -} - -func newL2Chain(chainID string, l1Wallets WalletMap, l2Wallets WalletMap, chainConfig *params.ChainConfig, l2Addresses AddressMap, nodes []Node) *l2Chain { - chain := &l2Chain{ - chain: newChain(chainID, l2Wallets, chainConfig, l2Addresses, nodes), - l1Wallets: l1Wallets, - } - return chain -} - -type l2Chain struct { - *chain - l1Wallets WalletMap -} - -func (c *l2Chain) L1Wallets() WalletMap { - return c.l1Wallets -} diff --git a/devnet-sdk/system/chain_test.go b/devnet-sdk/system/chain_test.go deleted file mode 100644 index 975f90ea6dc5a..0000000000000 --- a/devnet-sdk/system/chain_test.go +++ /dev/null @@ -1,246 +0,0 @@ -package system - -import ( - "context" - "math/big" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/registry/empty" - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" -) - -func TestClientManager(t *testing.T) { - manager := newClientManager() - - t.Run("returns error for invalid URL", func(t *testing.T) { - _, err := manager.Client("invalid://url") - assert.Error(t, err) - }) - - t.Run("caches client for same URL", func(t *testing.T) { - // Use a hostname that's guaranteed to fail DNS resolution - url := "http://this.domain.definitely.does.not.exist:8545" - - // First call should create new client - client1, err1 := manager.Client(url) - // Second call should return cached client - client2, err2 := manager.Client(url) - - // Both calls should succeed in creating a client - assert.NoError(t, err1) - assert.NoError(t, err2) - assert.NotNil(t, client1) - assert.NotNil(t, client2) - - // But the client should fail when used - ctx := context.Background() - _, err := client1.ChainID(ctx) - assert.Error(t, err) - - // And both clients should be the same instance - assert.Same(t, client1, client2) - }) -} - -func TestChainFromDescriptor(t *testing.T) { - descriptor := &descriptors.Chain{ - ID: "1", - Nodes: []descriptors.Node{ - { - Services: descriptors.ServiceMap{ - "el": &descriptors.Service{ - Endpoints: descriptors.EndpointMap{ - "rpc": &descriptors.PortInfo{ - Host: "localhost", - Port: 8545, - }, - }, - }, - }, - }, - }, - Wallets: descriptors.WalletMap{ - "user1": &descriptors.Wallet{ - PrivateKey: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - Address: common.HexToAddress("0x1234567890123456789012345678901234567890"), - }, - }, - Addresses: descriptors.AddressMap{ - "user1": common.HexToAddress("0x1234567890123456789012345678901234567890"), - }, - } - - chain, err := newChainFromDescriptor(descriptor) - assert.Nil(t, err) - assert.NotNil(t, chain) - assert.Equal(t, "http://localhost:8545", chain.Nodes()[0].RPCURL()) - - // Compare the underlying big.Int values - chainID := chain.ID() - expectedID := big.NewInt(1) - assert.Equal(t, 0, expectedID.Cmp(chainID)) -} - -func TestL2ChainFromDescriptor(t *testing.T) { - descriptor := &descriptors.L2Chain{ - Chain: &descriptors.Chain{ - ID: "1", - Nodes: []descriptors.Node{ - { - Services: descriptors.ServiceMap{ - "el": &descriptors.Service{ - Endpoints: descriptors.EndpointMap{ - "rpc": &descriptors.PortInfo{ - Host: "localhost", - Port: 8545, - }, - }, - }, - }, - }, - }, - Wallets: descriptors.WalletMap{ - "user1": &descriptors.Wallet{ - PrivateKey: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - Address: common.HexToAddress("0x1234567890123456789012345678901234567890"), - }, - }, - Addresses: descriptors.AddressMap{ - "user2": common.HexToAddress("0x1234567890123456789012345678901234567891"), - }, - }, - L1Wallets: descriptors.WalletMap{ - "user1": &descriptors.Wallet{ - PrivateKey: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - Address: common.HexToAddress("0x1234567890123456789012345678901234567890"), - }, - }, - } - - chain, err := newL2ChainFromDescriptor(descriptor) - assert.Nil(t, err) - assert.NotNil(t, chain) - assert.Equal(t, "http://localhost:8545", chain.Nodes()[0].RPCURL()) - - // Compare the underlying big.Int values - chainID := chain.ID() - expectedID := big.NewInt(1) - assert.Equal(t, 0, expectedID.Cmp(chainID)) -} - -func TestChainWallet(t *testing.T) { - testAddr := common.HexToAddress("0x1234567890123456789012345678901234567890") - - wallet, err := NewWallet("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", testAddr, nil) - assert.Nil(t, err) - - l1Chain := newChain("1", WalletMap{"user1": wallet}, nil, map[string]common.Address{}, []Node{}) - - t.Run("finds wallet meeting constraints", func(t *testing.T) { - constraint := &addressConstraint{addr: testAddr} - wallets := l1Chain.Wallets() - - for _, w := range wallets { - if constraint.CheckWallet(w) { - assert.NotNil(t, w) - assert.Equal(t, testAddr, w.Address()) - return - } - } - t.Fatalf("wallet not found") - }) - - t.Run("returns error when no wallet meets constraints", func(t *testing.T) { - wrongAddr := common.HexToAddress("0x0987654321098765432109876543210987654321") - constraint := &addressConstraint{addr: wrongAddr} - wallets := l1Chain.Wallets() - - for _, w := range wallets { - if constraint.CheckWallet(w) { - t.Fatalf("wallet found") - } - } - }) -} - -// addressConstraint implements constraints.WalletConstraint for testing -type addressConstraint struct { - addr common.Address -} - -func (c *addressConstraint) CheckWallet(w Wallet) bool { - return w.Address() == c.addr -} - -func TestChainID(t *testing.T) { - tests := []struct { - name string - idString string - want *big.Int - }{ - { - name: "valid chain ID", - idString: "1", - want: big.NewInt(1), - }, - { - name: "empty chain ID", - idString: "", - want: big.NewInt(0), - }, - { - name: "invalid chain ID", - idString: "not a number", - want: big.NewInt(0), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - chain := newChain(tt.idString, WalletMap{}, nil, AddressMap{}, []Node{}) - got := chain.ID() - // Compare the underlying big.Int values - assert.Equal(t, 0, tt.want.Cmp(got)) - }) - } -} - -func TestSupportsEIP(t *testing.T) { - ctx := context.Background() - chain := newChain("1", WalletMap{}, nil, AddressMap{}, []Node{}) - - // Since we can't reliably test against a live node, we're just testing the error case - t.Run("returns false for connection error", func(t *testing.T) { - assert.False(t, chain.SupportsEIP(ctx, 1559)) - assert.False(t, chain.SupportsEIP(ctx, 4844)) - }) -} - -// mockContractsRegistry extends empty.EmptyRegistry to provide mock contract instances -type mockContractsRegistry struct { - empty.EmptyRegistry -} - -func TestContractsRegistry(t *testing.T) { - node := &mockNode{} - // Create a mock for testing - mockRegistry := &mockContractsRegistry{} - - // Set up the mock to return the registry when ContractsRegistry() is called - node.On("ContractsRegistry").Return(mockRegistry) - - chain := newChain("1", WalletMap{}, nil, AddressMap{}, []Node{node}) - - t.Run("returns empty registry on error", func(t *testing.T) { - registry := chain.Nodes()[0].ContractsRegistry() - assert.NotNil(t, registry) - }) - - t.Run("caches registry", func(t *testing.T) { - registry1 := chain.Nodes()[0].ContractsRegistry() - registry2 := chain.Nodes()[0].ContractsRegistry() - assert.Same(t, registry1, registry2) - }) -} diff --git a/devnet-sdk/system/interfaces.go b/devnet-sdk/system/interfaces.go deleted file mode 100644 index eecb4952fef1a..0000000000000 --- a/devnet-sdk/system/interfaces.go +++ /dev/null @@ -1,150 +0,0 @@ -package system - -import ( - "context" - "crypto/ecdsa" - "math/big" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/sources" - supervisorTypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - coreTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/params" -) - -type System interface { - Identifier() string - L1() Chain - L2s() []L2Chain -} - -// Chain represents an Ethereum chain (L1 or L2) -type Chain interface { - ID() types.ChainID - Nodes() []Node // The node at index 0 will always be the sequencer node - Config() (*params.ChainConfig, error) - - // The wallets and addresses below are for use on the chain that the instance represents. - // If the instance also implements L2Chain, then the wallets and addresses below are still for the L2. - Wallets() WalletMap - Addresses() AddressMap -} - -type L2Chain interface { - Chain - - // The wallets below are for use on the L1 chain that this L2Chain instance settles to. - L1Wallets() WalletMap -} - -type Node interface { - Name() string - GasPrice(ctx context.Context) (*big.Int, error) - GasLimit(ctx context.Context, tx TransactionData) (uint64, error) - PendingNonceAt(ctx context.Context, address common.Address) (uint64, error) - BlockByNumber(ctx context.Context, number *big.Int) (eth.BlockInfo, error) - ContractsRegistry() interfaces.ContractsRegistry - SupportsEIP(ctx context.Context, eip uint64) bool - RPCURL() string - Client() (*sources.EthClient, error) - GethClient() (*ethclient.Client, error) -} - -type WalletMap map[string]Wallet -type AddressMap descriptors.AddressMap - -// Wallet represents a chain wallet. -// In particular it can process transactions. -type Wallet interface { - PrivateKey() types.Key - Address() types.Address - SendETH(to types.Address, amount types.Balance) types.WriteInvocation[any] - InitiateMessage(chainID types.ChainID, target common.Address, message []byte) types.WriteInvocation[any] - ExecuteMessage(identifier bindings.Identifier, sentMessage []byte) types.WriteInvocation[any] - Balance() types.Balance - Nonce() uint64 - - TransactionProcessor -} - -// WalletV2 is a temporary interface for integrating txplan and txintent -type WalletV2 interface { - PrivateKey() *ecdsa.PrivateKey - Address() common.Address - Client() *sources.EthClient - GethClient() *ethclient.Client - Ctx() context.Context -} - -// TransactionProcessor is a helper interface for signing and sending transactions. -type TransactionProcessor interface { - Sign(tx Transaction) (Transaction, error) - Send(ctx context.Context, tx Transaction) error -} - -// Transaction interfaces: - -// TransactionData is the input for a transaction creation. -type TransactionData interface { - From() common.Address - To() *common.Address - Value() *big.Int - Data() []byte - AccessList() coreTypes.AccessList -} - -// Transaction is the instantiated transaction object. -type Transaction interface { - Type() uint8 - Hash() common.Hash - TransactionData -} - -type Receipt interface { - BlockNumber() *big.Int - Logs() []*coreTypes.Log - TxHash() common.Hash -} - -// RawTransaction is an optional interface that can be implemented by a Transaction -// to provide access to the raw transaction data. -// It is currently necessary to perform processing operations (signing, sending) -// on the transaction. We would need to do better here. -type RawTransaction interface { - Raw() *coreTypes.Transaction -} - -// Specialized interop interfaces: - -// InteropSystem extends System with interoperability features -type InteropSystem interface { - System - InteropSet() InteropSet - Supervisor(context.Context) (Supervisor, error) -} - -// InteropSet provides access to L2 chains in an interop environment -type InteropSet interface { - L2s() []L2Chain -} - -// Supervisor provides access to the query interface of the supervisor -type Supervisor interface { - LocalUnsafe(context.Context, eth.ChainID) (eth.BlockID, error) - CrossSafe(context.Context, eth.ChainID) (supervisorTypes.DerivedIDPair, error) - Finalized(context.Context, eth.ChainID) (eth.BlockID, error) - FinalizedL1(context.Context) (eth.BlockRef, error) - CrossDerivedToSource(context.Context, eth.ChainID, eth.BlockID) (eth.BlockRef, error) - UpdateLocalUnsafe(context.Context, eth.ChainID, eth.BlockRef) error - UpdateLocalSafe(context.Context, eth.ChainID, eth.L1BlockRef, eth.BlockRef) error - SuperRootAtTimestamp(context.Context, hexutil.Uint64) (eth.SuperRootResponse, error) - AllSafeDerivedAt(context.Context, eth.BlockID) (derived map[eth.ChainID]eth.BlockID, err error) - SyncStatus(context.Context) (eth.SupervisorSyncStatus, error) -} diff --git a/devnet-sdk/system/node.go b/devnet-sdk/system/node.go deleted file mode 100644 index 31149d2e76c50..0000000000000 --- a/devnet-sdk/system/node.go +++ /dev/null @@ -1,139 +0,0 @@ -package system - -import ( - "context" - "fmt" - "math/big" - "sync" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts" - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum-optimism/optimism/op-service/bigs" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/sources" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" -) - -var ( - // This will make sure that we implement the Node interface - _ Node = (*node)(nil) -) - -type node struct { - rpcUrl string - name string - clients *clientManager - mu sync.Mutex - registry interfaces.ContractsRegistry -} - -func newNode(rpcUrl string, name string, clients *clientManager) *node { - return &node{rpcUrl: rpcUrl, name: name, clients: clients} -} - -func (n *node) GasPrice(ctx context.Context) (*big.Int, error) { - client, err := n.clients.Client(n.rpcUrl) - if err != nil { - return nil, fmt.Errorf("failed to get client: %w", err) - } - return client.SuggestGasPrice(ctx) -} - -func (n *node) GasLimit(ctx context.Context, tx TransactionData) (uint64, error) { - client, err := n.clients.Client(n.rpcUrl) - if err != nil { - return 0, fmt.Errorf("failed to get client: %w", err) - } - - msg := ethereum.CallMsg{ - From: tx.From(), - To: tx.To(), - Value: tx.Value(), - Data: tx.Data(), - AccessList: tx.AccessList(), - } - estimated, err := client.EstimateGas(ctx, msg) - if err != nil { - return 0, fmt.Errorf("failed to estimate gas: %w", err) - } - - return estimated, nil -} - -func (n *node) PendingNonceAt(ctx context.Context, address common.Address) (uint64, error) { - client, err := n.clients.Client(n.rpcUrl) - if err != nil { - return 0, fmt.Errorf("failed to get client: %w", err) - } - return client.PendingNonceAt(ctx, address) -} - -func (n *node) BlockByNumber(ctx context.Context, number *big.Int) (eth.BlockInfo, error) { - client, err := n.clients.Client(n.rpcUrl) - if err != nil { - return nil, fmt.Errorf("failed to get client: %w", err) - } - var block eth.BlockInfo - if number != nil { - block, err = client.InfoByNumber(ctx, bigs.Uint64Strict(number)) - } else { - block, err = client.InfoByLabel(ctx, eth.Unsafe) - } - if err != nil { - return nil, err - } - return block, nil -} - -func (n *node) Client() (*sources.EthClient, error) { - return n.clients.Client(n.rpcUrl) -} - -func (n *node) GethClient() (*ethclient.Client, error) { - return n.clients.GethClient(n.rpcUrl) -} - -func (n *node) ContractsRegistry() interfaces.ContractsRegistry { - n.mu.Lock() - defer n.mu.Unlock() - - if n.registry != nil { - return n.registry - } - client, err := n.clients.GethClient(n.rpcUrl) - if err != nil { - return contracts.NewEmptyRegistry() - } - - n.registry = contracts.NewClientRegistry(client) - return n.registry -} - -func (n *node) RPCURL() string { - return n.rpcUrl -} - -func (n *node) SupportsEIP(ctx context.Context, eip uint64) bool { - client, err := n.Client() - if err != nil { - return false - } - - switch eip { - case 1559: - return checkHeader(ctx, client, func(h eth.BlockInfo) bool { - return h.BaseFee() != nil - }) - case 4844: - return checkHeader(ctx, client, func(h eth.BlockInfo) bool { - return h.ExcessBlobGas() != nil - }) - } - return false -} - -func (n *node) Name() string { - return n.name -} diff --git a/devnet-sdk/system/periphery/go-ethereum/fees.go b/devnet-sdk/system/periphery/go-ethereum/fees.go deleted file mode 100644 index 0508d195e7384..0000000000000 --- a/devnet-sdk/system/periphery/go-ethereum/fees.go +++ /dev/null @@ -1,134 +0,0 @@ -package goethereum - -import ( - "context" - "math/big" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" -) - -var ( - // Ensure that the feeEstimator implements the FeeEstimator interface - _ FeeEstimator = (*EIP1559FeeEstimator)(nil) - - // Ensure that the EIP1159FeeEthClient implements the EIP1159FeeEthClient interface - _ EIP1159FeeEthClient = (*ethclient.Client)(nil) -) - -// FeeEstimator is a generic fee estimation interface (not specific to EIP-1559) -type FeeEstimator interface { - EstimateFees(ctx context.Context, opts *bind.TransactOpts) (*bind.TransactOpts, error) -} - -// EIP1559FeeEstimator is a fee estimator that uses EIP-1559 fee estimation -type EIP1559FeeEstimator struct { - // Access to the Ethereum client is needed to get the fee information from the chain - client EIP1159FeeEthClient - - options eip1559FeeEstimatorOptions -} - -type eip1559FeeEstimatorOptions struct { - // The base multiplier is used to increase the maxFeePerGas (GasFeeCap) by a factor - baseMultiplier float64 - - // The tip multiplier is used to increase the maxPriorityFeePerGas (GasTipCap) by a factor - tipMultiplier float64 -} - -type EIP1559FeeEstimatorOption interface { - apply(*eip1559FeeEstimatorOptions) -} - -type eip1559FeeEstimatorOptionBaseMultiplier float64 - -func (o eip1559FeeEstimatorOptionBaseMultiplier) apply(opts *eip1559FeeEstimatorOptions) { - opts.baseMultiplier = float64(o) -} - -func WithEIP1559BaseMultiplier(multiplier float64) EIP1559FeeEstimatorOption { - return eip1559FeeEstimatorOptionBaseMultiplier(multiplier) -} - -type eip1559FeeEstimatorOptionTipMultiplier float64 - -func (o eip1559FeeEstimatorOptionTipMultiplier) apply(opts *eip1559FeeEstimatorOptions) { - opts.tipMultiplier = float64(o) -} - -func WithEIP1559TipMultiplier(multiplier float64) EIP1559FeeEstimatorOption { - return eip1559FeeEstimatorOptionTipMultiplier(multiplier) -} - -func NewEIP1559FeeEstimator(client EIP1159FeeEthClient, opts ...EIP1559FeeEstimatorOption) *EIP1559FeeEstimator { - options := eip1559FeeEstimatorOptions{ - baseMultiplier: 1.0, - tipMultiplier: 1.0, - } - - for _, o := range opts { - o.apply(&options) - } - - return &EIP1559FeeEstimator{ - client: client, - options: options, - } -} - -func (f *EIP1559FeeEstimator) EstimateFees(ctx context.Context, opts *bind.TransactOpts) (*bind.TransactOpts, error) { - newOpts := *opts - - // Add a gas tip cap if needed - if newOpts.GasTipCap == nil { - tipCap, err := f.client.SuggestGasTipCap(ctx) - - if err != nil { - return nil, err - } - - // GasTipCap represents the maxPriorityFeePerGas - newOpts.GasTipCap = multiplyBigInt(tipCap, f.options.tipMultiplier) - } - - // Add a gas fee cap if needed - if newOpts.GasFeeCap == nil { - block, err := f.client.BlockByNumber(ctx, nil) - if err != nil { - return nil, err - } - - baseFee := block.BaseFee() - if baseFee != nil { - // The adjusted base fee takes the multiplier into account - adjustedBaseFee := multiplyBigInt(baseFee, f.options.baseMultiplier) - - // The total fee (maxFeePerGas) is the sum of the base fee and the tip - newOpts.GasFeeCap = big.NewInt(0).Add(adjustedBaseFee, newOpts.GasTipCap) - } - } - - return &newOpts, nil -} - -// EIP1159FeeEthClient is a subset of the ethclient.Client interface required for fee estimation -type EIP1159FeeEthClient interface { - BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) - SuggestGasTipCap(ctx context.Context) (*big.Int, error) -} - -// multiplyBigInt is a little helper for a messy big.Int x float64 multiplication -// -// It rounds the result away from zero since that's the lower risk behavior for fee estimation -func multiplyBigInt(b *big.Int, m float64) *big.Int { - bFloat := big.NewFloat(0).SetInt(b) - mFloat := big.NewFloat(m) - ceiled, accuracy := big.NewFloat(0).Mul(bFloat, mFloat).Int(nil) - if accuracy == big.Below { - ceiled = ceiled.Add(ceiled, big.NewInt(1)) - } - - return ceiled -} diff --git a/devnet-sdk/system/periphery/go-ethereum/fees_test.go b/devnet-sdk/system/periphery/go-ethereum/fees_test.go deleted file mode 100644 index 302d6f556acc6..0000000000000 --- a/devnet-sdk/system/periphery/go-ethereum/fees_test.go +++ /dev/null @@ -1,276 +0,0 @@ -package goethereum - -import ( - "context" - "fmt" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestMultiplyBigInt(t *testing.T) { - type TestCase struct { - value *big.Int - multiplier float64 - expected *big.Int - } - - testCases := []TestCase{ - { - value: big.NewInt(10), - multiplier: 1.0, - expected: big.NewInt(10), - }, - { - value: big.NewInt(7), - multiplier: 0.0, - expected: big.NewInt(0), - }, - { - value: big.NewInt(10), - multiplier: 1.01, - expected: big.NewInt(11), - }, - { - value: big.NewInt(10), - multiplier: 1.11, - expected: big.NewInt(12), - }, - { - value: big.NewInt(5), - multiplier: 1.2, - expected: big.NewInt(6), - }, - } - - for _, testCase := range testCases { - t.Run(fmt.Sprintf("should return %d for %d multiplied by %f", testCase.expected.Int64(), testCase.value.Int64(), testCase.multiplier), func(t *testing.T) { - result := multiplyBigInt(testCase.value, testCase.multiplier) - require.Equal(t, testCase.expected, result) - }) - } -} - -func TestEstimateEIP1559Fees(t *testing.T) { - t.Run("if GasFeeCap and GasTipCap are not nil", func(t *testing.T) { - opts := &bind.TransactOpts{ - GasFeeCap: big.NewInt(1), - GasTipCap: big.NewInt(2), - } - - t.Run("should not modify the options", func(t *testing.T) { - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{}) - newOpts, err := feeEstimator.EstimateFees(context.Background(), opts) - require.NoError(t, err) - - require.Equal(t, opts, newOpts) - - // We make sure that we get a copy of the object to prevent mutating the original - assert.NotSame(t, opts, newOpts) - }) - }) - - t.Run("if the GasTipCap is nil", func(t *testing.T) { - defaultOpts := &bind.TransactOpts{ - GasFeeCap: big.NewInt(1), - From: common.Address{}, - Nonce: big.NewInt(64), - } - - t.Run("should return an error if the client returns an error", func(t *testing.T) { - tipCapErr := fmt.Errorf("tip cap error") - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{ - tipCapErr: tipCapErr, - }) - _, err := feeEstimator.EstimateFees(context.Background(), defaultOpts) - require.Equal(t, tipCapErr, err) - }) - - t.Run("with default tip multiplier", func(t *testing.T) { - t.Run("should set the GasTipCap to the client's suggested tip cap", func(t *testing.T) { - tipCapValue := big.NewInt(5) - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{ - tipCapValue: tipCapValue, - }) - - newOpts, err := feeEstimator.EstimateFees(context.Background(), defaultOpts) - require.NoError(t, err) - - // We create a new opts with the expected tip cap added - expectedOpts := *defaultOpts - expectedOpts.GasTipCap = tipCapValue - - // We check that the tip has been added - require.Equal(t, &expectedOpts, newOpts) - - // We make sure that we get a copy of the object to prevent mutating the original - assert.NotSame(t, defaultOpts, newOpts) - }) - }) - - t.Run("with custom tip multiplier", func(t *testing.T) { - t.Run("should set the GasTipCap to the client's suggested tip cap multiplied by the tip multiplier", func(t *testing.T) { - tipCapValue := big.NewInt(5) - tipMultiplier := 10.0 - // The expected tip is a product of the tip cap and the tip multiplier - expectedTip := big.NewInt(50) - - // We create a fee estimator with a custom tip multiplier - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{ - tipCapValue: tipCapValue, - }, WithEIP1559TipMultiplier(tipMultiplier)) - - newOpts, err := feeEstimator.EstimateFees(context.Background(), defaultOpts) - require.NoError(t, err) - - // We create a new opts with the expected tip cap added - expectedOpts := *defaultOpts - expectedOpts.GasTipCap = expectedTip - - // We check that the tip has been added - require.Equal(t, &expectedOpts, newOpts) - - // We make sure that we get a copy of the object to prevent mutating the original - assert.NotSame(t, defaultOpts, newOpts) - }) - }) - }) - - t.Run("if the GasFeeCap is nil", func(t *testing.T) { - defaultOpts := &bind.TransactOpts{ - GasTipCap: big.NewInt(1), - From: common.Address{}, - Nonce: big.NewInt(64), - } - - t.Run("should return an error if the client returns an error", func(t *testing.T) { - blockErr := fmt.Errorf("tip cap error") - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{ - blockErr: blockErr, - }) - _, err := feeEstimator.EstimateFees(context.Background(), defaultOpts) - require.Equal(t, blockErr, err) - }) - - t.Run("should set the GasFeeCap to the sum of block base fee and tip", func(t *testing.T) { - baseFeeValue := big.NewInt(5) - blockValue := types.NewBlock(&types.Header{ - BaseFee: baseFeeValue, - Time: 0, - }, nil, nil, nil, &mockBlockType{}) - - // We expect the total gas cap to be the base fee plus the tip cap - expectedGas := big.NewInt(0).Add(baseFeeValue, defaultOpts.GasTipCap) - - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{ - blockValue: blockValue, - }) - - newOpts, err := feeEstimator.EstimateFees(context.Background(), defaultOpts) - require.NoError(t, err) - - // We create a new opts with the expected fee cap added - expectedOpts := *defaultOpts - expectedOpts.GasFeeCap = expectedGas - - // We check that the tip has been added - require.Equal(t, &expectedOpts, newOpts) - - // We make sure that we get a copy of the object to prevent mutating the original - assert.NotSame(t, defaultOpts, newOpts) - }) - - t.Run("should set the GasFeeCap to nil if the base fee is nil", func(t *testing.T) { - blockValue := types.NewBlock(&types.Header{ - BaseFee: nil, - Time: 0, - }, nil, nil, nil, &mockBlockType{}) - - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{ - blockValue: blockValue, - }) - - newOpts, err := feeEstimator.EstimateFees(context.Background(), defaultOpts) - require.NoError(t, err) - - // We create a new opts with the expected fee cap added - expectedOpts := *defaultOpts - expectedOpts.GasFeeCap = nil - - // We check that the tip has been added - require.Equal(t, &expectedOpts, newOpts) - - // We make sure that we get a copy of the object to prevent mutating the original - assert.NotSame(t, defaultOpts, newOpts) - }) - - t.Run("with custom base multiplier", func(t *testing.T) { - t.Run("should set the GasFeeCap to the block base fee multiplied by the base multiplier", func(t *testing.T) { - baseMultiplier := 1.2 - baseFeeValue := big.NewInt(9) - blockValue := types.NewBlock(&types.Header{ - BaseFee: baseFeeValue, - Time: 0, - }, nil, nil, nil, &mockBlockType{}) - - // We expect the total gas cap to be the base fee (9) multiplied by 1.2 (= 10.8, rounded up to 11) plus the tip cap (1) - expectedGas := big.NewInt(0).Add(big.NewInt(11), defaultOpts.GasTipCap) - - // We create a fee estimator with a custom tip multiplier - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{ - blockValue: blockValue, - }, WithEIP1559BaseMultiplier(baseMultiplier)) - - newOpts, err := feeEstimator.EstimateFees(context.Background(), defaultOpts) - require.NoError(t, err) - - // We create a new opts with the expected tip cap added - expectedOpts := *defaultOpts - expectedOpts.GasFeeCap = expectedGas - - // We check that the tip has been added - require.Equal(t, &expectedOpts, newOpts) - - // We make sure that we get a copy of the object to prevent mutating the original - assert.NotSame(t, defaultOpts, newOpts) - }) - }) - }) -} - -var ( - _ EIP1159FeeEthClient = (*mockFeeEthClientImpl)(nil) - - _ types.BlockType = (*mockBlockType)(nil) -) - -type mockFeeEthClientImpl struct { - blockValue *types.Block - blockErr error - - tipCapValue *big.Int - tipCapErr error -} - -func (m *mockFeeEthClientImpl) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { - return m.blockValue, m.blockErr -} - -func (m *mockFeeEthClientImpl) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { - return m.tipCapValue, m.tipCapErr -} - -type mockBlockType struct{} - -func (m *mockBlockType) HasOptimismWithdrawalsRoot(blkTime uint64) bool { - return false -} - -func (m *mockBlockType) IsIsthmus(blkTime uint64) bool { - return false -} diff --git a/devnet-sdk/system/system.go b/devnet-sdk/system/system.go deleted file mode 100644 index d282e9ee01d4d..0000000000000 --- a/devnet-sdk/system/system.go +++ /dev/null @@ -1,110 +0,0 @@ -package system - -import ( - "context" - "fmt" - "slices" - "sync" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/devnet-sdk/shell/env" - "github.com/ethereum-optimism/optimism/op-service/dial" -) - -type system struct { - identifier string - l1 Chain - l2s []L2Chain -} - -// system implements System -var _ System = (*system)(nil) - -func NewSystemFromURL(url string) (System, error) { - devnetEnv, err := env.LoadDevnetFromURL(url) - if err != nil { - return nil, fmt.Errorf("failed to load devnet from URL: %w", err) - } - - sys, err := systemFromDevnet(devnetEnv.Env) - if err != nil { - return nil, fmt.Errorf("failed to create system from devnet: %w", err) - } - return sys, nil -} - -func (s *system) L1() Chain { - return s.l1 -} - -func (s *system) L2s() []L2Chain { - return s.l2s -} - -func (s *system) Identifier() string { - return s.identifier -} - -func systemFromDevnet(dn *descriptors.DevnetEnvironment) (System, error) { - l1, err := newChainFromDescriptor(dn.L1) - if err != nil { - return nil, fmt.Errorf("failed to add L1 chain: %w", err) - } - - l2s := make([]L2Chain, len(dn.L2)) - for i, l2 := range dn.L2 { - l2s[i], err = newL2ChainFromDescriptor(l2) - if err != nil { - return nil, fmt.Errorf("failed to add L2 chain: %w", err) - } - } - - sys := &system{ - identifier: dn.Name, - l1: l1, - l2s: l2s, - } - - if slices.Contains(dn.Features, "interop") { - // TODO(14849): this will break as soon as we have a dependency set that - // doesn't include all L2s. - supervisorRPC := dn.L2[0].Services["supervisor"][0].Endpoints["rpc"] - return &interopSystem{ - system: sys, - supervisorRPC: fmt.Sprintf("http://%s:%d", supervisorRPC.Host, supervisorRPC.Port), - }, nil - } - - return sys, nil -} - -type interopSystem struct { - *system - - supervisorRPC string - supervisor Supervisor - mu sync.Mutex -} - -// interopSystem implements InteropSystem -var _ InteropSystem = (*interopSystem)(nil) - -func (i *interopSystem) InteropSet() InteropSet { - return i.system // TODO: the interop set might not contain all L2s -} - -func (i *interopSystem) Supervisor(ctx context.Context) (Supervisor, error) { - i.mu.Lock() - defer i.mu.Unlock() - - if i.supervisor != nil { - return i.supervisor, nil - } - - supervisor, err := dial.DialSupervisorClientWithTimeout(ctx, nil, i.supervisorRPC) - if err != nil { - return nil, fmt.Errorf("failed to dial supervisor RPC: %w", err) - } - i.supervisor = supervisor - return supervisor, nil -} diff --git a/devnet-sdk/system/system_test.go b/devnet-sdk/system/system_test.go deleted file mode 100644 index f97bbdec532b7..0000000000000 --- a/devnet-sdk/system/system_test.go +++ /dev/null @@ -1,273 +0,0 @@ -package system - -import ( - "encoding/json" - "os" - "path/filepath" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewSystemFromEnv(t *testing.T) { - // Create a temporary devnet file - tempDir := t.TempDir() - devnetFile := filepath.Join(tempDir, "devnet.json") - - devnet := &descriptors.DevnetEnvironment{ - L1: &descriptors.Chain{ - ID: "1", - Nodes: []descriptors.Node{{ - Services: map[string]*descriptors.Service{ - "el": { - Name: "geth", - Endpoints: descriptors.EndpointMap{ - "rpc": &descriptors.PortInfo{ - Host: "localhost", - Port: 8545, - }, - }, - }, - }, - }}, - Wallets: descriptors.WalletMap{ - "default": &descriptors.Wallet{ - Address: common.HexToAddress("0x123"), - PrivateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - }, - }, - Addresses: descriptors.AddressMap{ - "defaultl1": common.HexToAddress("0x123"), - }, - }, - L2: []*descriptors.L2Chain{ - { - Chain: &descriptors.Chain{ - ID: "2", - Nodes: []descriptors.Node{{ - Services: map[string]*descriptors.Service{ - "el": { - Name: "geth", - Endpoints: descriptors.EndpointMap{ - "rpc": &descriptors.PortInfo{ - Host: "localhost", - Port: 8546, - }, - }, - }, - }, - }}, - Wallets: descriptors.WalletMap{ - "default": &descriptors.Wallet{ - Address: common.HexToAddress("0x123"), - PrivateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - }, - }, - Addresses: descriptors.AddressMap{ - "defaultl2": common.HexToAddress("0x456"), - }, - }, - L1Wallets: descriptors.WalletMap{ - "default": &descriptors.Wallet{ - Address: common.HexToAddress("0x123"), - PrivateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - }, - }, - }, - }, - Features: []string{}, - } - - data, err := json.Marshal(devnet) - require.NoError(t, err) - require.NoError(t, os.WriteFile(devnetFile, data, 0644)) - - sys, err := NewSystemFromURL(devnetFile) - assert.NoError(t, err) - assert.NotNil(t, sys) -} - -func TestSystemFromDevnet(t *testing.T) { - testNode := descriptors.Node{ - Services: map[string]*descriptors.Service{ - "el": { - Name: "geth", - Endpoints: descriptors.EndpointMap{ - "rpc": &descriptors.PortInfo{ - Host: "localhost", - Port: 8545, - }, - }, - }, - }, - } - - testWallet := &descriptors.Wallet{ - Address: common.HexToAddress("0x123"), - PrivateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - } - - tests := []struct { - name string - devnet *descriptors.DevnetEnvironment - wantErr bool - isInterop bool - }{ - { - name: "basic system", - devnet: &descriptors.DevnetEnvironment{ - L1: &descriptors.Chain{ - ID: "1", - Nodes: []descriptors.Node{testNode}, - Wallets: descriptors.WalletMap{ - "default": testWallet, - }, - Addresses: descriptors.AddressMap{ - "defaultl1": common.HexToAddress("0x123"), - }, - }, - L2: []*descriptors.L2Chain{ - { - Chain: &descriptors.Chain{ - ID: "2", - Nodes: []descriptors.Node{testNode}, - Wallets: descriptors.WalletMap{ - "default": testWallet, - }, - }, - L1Wallets: descriptors.WalletMap{ - "default": testWallet, - }, - }, - }, - }, - wantErr: false, - isInterop: false, - }, - { - name: "interop system", - devnet: &descriptors.DevnetEnvironment{ - L1: &descriptors.Chain{ - ID: "1", - Nodes: []descriptors.Node{testNode}, - Wallets: descriptors.WalletMap{ - "default": testWallet, - }, - Addresses: descriptors.AddressMap{ - "defaultl1": common.HexToAddress("0x123"), - }, - }, - L2: []*descriptors.L2Chain{ - { - Chain: &descriptors.Chain{ - ID: "2", - Nodes: []descriptors.Node{testNode}, - Wallets: descriptors.WalletMap{ - "default": testWallet, - }, - Services: descriptors.RedundantServiceMap{ - "supervisor": []*descriptors.Service{ - &descriptors.Service{ - Name: "supervisor", - Endpoints: descriptors.EndpointMap{ - "rpc": &descriptors.PortInfo{ - Host: "localhost", - Port: 8545, - }, - }, - }, - }, - }, - }, - L1Wallets: descriptors.WalletMap{ - "default": testWallet, - }, - }}, - Features: []string{"interop"}, - }, - wantErr: false, - isInterop: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - sys, err := systemFromDevnet(tt.devnet) - if tt.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - assert.NotNil(t, sys) - - _, isInterop := sys.(InteropSystem) - assert.Equal(t, tt.isInterop, isInterop) - }) - } -} - -func TestWallet(t *testing.T) { - chain := newChain("1", WalletMap{}, nil, AddressMap{}, []Node{}) - tests := []struct { - name string - privateKey string - address types.Address - wantAddr types.Address - wantPrivKey types.Key - }{ - { - name: "valid wallet", - privateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - address: common.HexToAddress("0x123"), - wantAddr: common.HexToAddress("0x123"), - }, - { - name: "empty wallet", - privateKey: "", - address: common.HexToAddress("0x123"), - wantAddr: common.HexToAddress("0x123"), - }, - { - name: "only address", - privateKey: "", - address: common.HexToAddress("0x456"), - wantAddr: common.HexToAddress("0x456"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - w, err := NewWallet(tt.privateKey, tt.address, chain) - assert.Nil(t, err) - - assert.Equal(t, tt.wantAddr, w.Address()) - }) - } -} - -func TestChainUser(t *testing.T) { - chain := newChain("1", WalletMap{}, nil, AddressMap{}, []Node{}) - - testWallet, err := NewWallet("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", common.HexToAddress("0x123"), chain) - assert.Nil(t, err) - - chain.wallets = WalletMap{ - "l2Faucet": testWallet, - } - - wallets := chain.Wallets() - require.NoError(t, err) - - for _, w := range wallets { - if w.Address() == testWallet.Address() { - assert.Equal(t, testWallet.Address(), w.Address()) - assert.Equal(t, testWallet.PrivateKey(), w.PrivateKey()) - return - } - } - assert.Fail(t, "wallet not found") -} diff --git a/devnet-sdk/system/tx.go b/devnet-sdk/system/tx.go deleted file mode 100644 index 41a661bd9539a..0000000000000 --- a/devnet-sdk/system/tx.go +++ /dev/null @@ -1,227 +0,0 @@ -package system - -import ( - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto/kzg4844" -) - -// TxOpts is a struct that holds all transaction options -type TxOpts struct { - from common.Address - to *common.Address - value *big.Int - data []byte - gasLimit uint64 // Optional: if 0, will be estimated - accessList types.AccessList - blobHashes []common.Hash - blobs []kzg4844.Blob - commitments []kzg4844.Commitment - proofs []kzg4844.Proof -} - -var _ TransactionData = (*TxOpts)(nil) - -func (opts *TxOpts) From() common.Address { - return opts.from -} - -func (opts *TxOpts) To() *common.Address { - return opts.to -} - -func (opts *TxOpts) Value() *big.Int { - return opts.value -} - -func (opts *TxOpts) Data() []byte { - return opts.data -} - -func (opts *TxOpts) AccessList() types.AccessList { - return opts.accessList -} - -// Validate checks that all required fields are set and consistent -func (opts *TxOpts) Validate() error { - // Check mandatory fields - if opts.from == (common.Address{}) { - return fmt.Errorf("from address is required") - } - if opts.to == nil { - return fmt.Errorf("to address is required") - } - if opts.value == nil || opts.value.Sign() < 0 { - return fmt.Errorf("value must be non-negative") - } - - // Check blob-related fields consistency - hasBlobs := len(opts.blobs) > 0 - hasCommitments := len(opts.commitments) > 0 - hasProofs := len(opts.proofs) > 0 - hasBlobHashes := len(opts.blobHashes) > 0 - - // If any blob-related field is set, all must be set - if hasBlobs || hasCommitments || hasProofs || hasBlobHashes { - if !hasBlobs { - return fmt.Errorf("blobs are required when other blob fields are set") - } - if !hasCommitments { - return fmt.Errorf("commitments are required when other blob fields are set") - } - if !hasProofs { - return fmt.Errorf("proofs are required when other blob fields are set") - } - if !hasBlobHashes { - return fmt.Errorf("blob hashes are required when other blob fields are set") - } - - // Check that all blob-related fields have the same length - blobCount := len(opts.blobs) - if len(opts.commitments) != blobCount { - return fmt.Errorf("number of commitments (%d) does not match number of blobs (%d)", len(opts.commitments), blobCount) - } - if len(opts.proofs) != blobCount { - return fmt.Errorf("number of proofs (%d) does not match number of blobs (%d)", len(opts.proofs), blobCount) - } - if len(opts.blobHashes) != blobCount { - return fmt.Errorf("number of blob hashes (%d) does not match number of blobs (%d)", len(opts.blobHashes), blobCount) - } - } - - return nil -} - -// TxOption is a function that configures TxOpts -type TxOption func(*TxOpts) - -// WithFrom sets the sender address -func WithFrom(from common.Address) TxOption { - return func(opts *TxOpts) { - opts.from = from - } -} - -// WithTo sets the recipient address -func WithTo(to common.Address) TxOption { - return func(opts *TxOpts) { - opts.to = &to - } -} - -// WithValue sets the transaction value -func WithValue(value *big.Int) TxOption { - return func(opts *TxOpts) { - opts.value = value - } -} - -// WithData sets the transaction data -func WithData(data []byte) TxOption { - return func(opts *TxOpts) { - opts.data = data - } -} - -// WithGasLimit sets an explicit gas limit -func WithGasLimit(gasLimit uint64) TxOption { - return func(opts *TxOpts) { - opts.gasLimit = gasLimit - } -} - -// WithAccessList sets the access list for EIP-2930 transactions -func WithAccessList(accessList types.AccessList) TxOption { - return func(opts *TxOpts) { - opts.accessList = accessList - } -} - -// WithBlobs sets the blob transaction fields -func WithBlobs(blobs []kzg4844.Blob) TxOption { - return func(opts *TxOpts) { - opts.blobs = blobs - } -} - -// WithBlobCommitments sets the blob commitments -func WithBlobCommitments(commitments []kzg4844.Commitment) TxOption { - return func(opts *TxOpts) { - opts.commitments = commitments - } -} - -// WithBlobProofs sets the blob proofs -func WithBlobProofs(proofs []kzg4844.Proof) TxOption { - return func(opts *TxOpts) { - opts.proofs = proofs - } -} - -// WithBlobHashes sets the blob hashes -func WithBlobHashes(hashes []common.Hash) TxOption { - return func(opts *TxOpts) { - opts.blobHashes = hashes - } -} - -// EthTx is the default implementation of Transaction that wraps types.Transaction -type EthTx struct { - tx *types.Transaction - from common.Address - txType uint8 -} - -func (t *EthTx) Hash() common.Hash { - return t.tx.Hash() -} - -func (t *EthTx) From() common.Address { - return t.from -} - -func (t *EthTx) To() *common.Address { - return t.tx.To() -} - -func (t *EthTx) Value() *big.Int { - return t.tx.Value() -} - -func (t *EthTx) Data() []byte { - return t.tx.Data() -} - -func (t *EthTx) AccessList() types.AccessList { - return t.tx.AccessList() -} - -func (t *EthTx) Type() uint8 { - return t.txType -} - -func (t *EthTx) Raw() *types.Transaction { - return t.tx -} - -// EthReceipt is the default implementation of Receipt that wraps types.Receipt -type EthReceipt struct { - blockNumber *big.Int - logs []*types.Log - txHash common.Hash -} - -func (t *EthReceipt) BlockNumber() *big.Int { - return t.blockNumber -} - -func (t *EthReceipt) Logs() []*types.Log { - return t.logs -} - -func (t *EthReceipt) TxHash() common.Hash { - return t.txHash -} diff --git a/devnet-sdk/system/tx_test.go b/devnet-sdk/system/tx_test.go deleted file mode 100644 index e9ec8137b17d2..0000000000000 --- a/devnet-sdk/system/tx_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package system - -import ( - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto/kzg4844" - "github.com/stretchr/testify/assert" -) - -func TestTxOpts_Validate(t *testing.T) { - addr := common.HexToAddress("0x1234567890123456789012345678901234567890") - tests := []struct { - name string - opts *TxOpts - wantErr bool - }{ - { - name: "valid basic transaction", - opts: &TxOpts{ - from: addr, - to: &addr, - value: big.NewInt(0), - }, - wantErr: false, - }, - { - name: "missing from address", - opts: &TxOpts{ - to: &addr, - value: big.NewInt(0), - }, - wantErr: true, - }, - { - name: "missing to address", - opts: &TxOpts{ - from: addr, - value: big.NewInt(0), - }, - wantErr: true, - }, - { - name: "negative value", - opts: &TxOpts{ - from: addr, - to: &addr, - value: big.NewInt(-1), - }, - wantErr: true, - }, - { - name: "valid with blobs", - opts: &TxOpts{ - from: addr, - to: &addr, - value: big.NewInt(0), - blobs: []kzg4844.Blob{{1}}, - commitments: []kzg4844.Commitment{{2}}, - proofs: []kzg4844.Proof{{3}}, - blobHashes: []common.Hash{{4}}, - }, - wantErr: false, - }, - { - name: "inconsistent blob fields - missing blobs", - opts: &TxOpts{ - from: addr, - to: &addr, - value: big.NewInt(0), - commitments: []kzg4844.Commitment{{2}}, - proofs: []kzg4844.Proof{{3}}, - blobHashes: []common.Hash{{4}}, - }, - wantErr: true, - }, - { - name: "inconsistent blob fields - mismatched lengths", - opts: &TxOpts{ - from: addr, - to: &addr, - value: big.NewInt(0), - blobs: []kzg4844.Blob{{1}}, - commitments: []kzg4844.Commitment{{2}, {3}}, // Extra commitment - proofs: []kzg4844.Proof{{3}}, - blobHashes: []common.Hash{{4}}, - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.opts.Validate() - if tt.wantErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestTxOpts_Getters(t *testing.T) { - addr := common.HexToAddress("0x1234567890123456789012345678901234567890") - value := big.NewInt(123) - data := []byte{1, 2, 3} - - opts := &TxOpts{ - from: addr, - to: &addr, - value: value, - data: data, - } - - assert.Equal(t, addr, opts.From()) - assert.Equal(t, &addr, opts.To()) - assert.Equal(t, value, opts.Value()) - assert.Equal(t, data, opts.Data()) -} - -func TestEthTx_Methods(t *testing.T) { - addr := common.HexToAddress("0x1234567890123456789012345678901234567890") - value := big.NewInt(123) - data := []byte{1, 2, 3} - - // Create a legacy transaction for testing - tx := types.NewTransaction( - 0, // nonce - addr, // to - value, // value - 21000, // gas limit - big.NewInt(1), // gas price - data, // data - ) - - ethTx := &EthTx{ - tx: tx, - from: addr, - txType: uint8(types.LegacyTxType), - } - - assert.Equal(t, tx.Hash(), ethTx.Hash()) - assert.Equal(t, addr, ethTx.From()) - assert.Equal(t, &addr, ethTx.To()) - assert.Equal(t, value, ethTx.Value()) - assert.Equal(t, data, ethTx.Data()) - assert.Equal(t, uint8(types.LegacyTxType), ethTx.Type()) - assert.Equal(t, tx, ethTx.Raw()) -} - -func TestTxOptions(t *testing.T) { - addr := common.HexToAddress("0x1234567890123456789012345678901234567890") - value := big.NewInt(123) - data := []byte{1, 2, 3} - gasLimit := uint64(21000) - accessList := types.AccessList{{ - Address: addr, - StorageKeys: []common.Hash{{1}}, - }} - blobs := []kzg4844.Blob{{1}} - commitments := []kzg4844.Commitment{{2}} - proofs := []kzg4844.Proof{{3}} - blobHashes := []common.Hash{{4}} - - opts := &TxOpts{} - - // Apply all options - WithFrom(addr)(opts) - WithTo(addr)(opts) - WithValue(value)(opts) - WithData(data)(opts) - WithGasLimit(gasLimit)(opts) - WithAccessList(accessList)(opts) - WithBlobs(blobs)(opts) - WithBlobCommitments(commitments)(opts) - WithBlobProofs(proofs)(opts) - WithBlobHashes(blobHashes)(opts) - - // Verify all fields were set correctly - assert.Equal(t, addr, opts.from) - assert.Equal(t, &addr, opts.to) - assert.Equal(t, value, opts.value) - assert.Equal(t, data, opts.data) - assert.Equal(t, gasLimit, opts.gasLimit) - assert.Equal(t, accessList, opts.accessList) - assert.Equal(t, blobs, opts.blobs) - assert.Equal(t, commitments, opts.commitments) - assert.Equal(t, proofs, opts.proofs) - assert.Equal(t, blobHashes, opts.blobHashes) -} diff --git a/devnet-sdk/system/txbuilder.go b/devnet-sdk/system/txbuilder.go deleted file mode 100644 index 836210b1fbbb7..0000000000000 --- a/devnet-sdk/system/txbuilder.go +++ /dev/null @@ -1,360 +0,0 @@ -package system - -import ( - "context" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/log" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/holiman/uint256" -) - -// Default values for gas calculations -const ( - DefaultGasLimitMarginPercent = 20 // 20% margin for gas limit - DefaultFeeCapMultiplier = 2 // 2x gas price for fee cap -) - -// TxBuilderOption is a function that configures a TxBuilder -type TxBuilderOption func(*TxBuilder) - -// WithTxType sets the transaction type to use, overriding automatic detection -func WithTxType(txType uint8) TxBuilderOption { - return func(b *TxBuilder) { - b.forcedTxType = &txType - b.supportedTxTypes = []uint8{txType} - } -} - -// WithGasLimitMargin sets the margin percentage to add to estimated gas limit -func WithGasLimitMargin(marginPercent uint64) TxBuilderOption { - return func(b *TxBuilder) { - b.gasLimitMarginPercent = marginPercent - } -} - -// WithFeeCapMultiplier sets the multiplier for calculating fee cap from gas price -func WithFeeCapMultiplier(multiplier uint64) TxBuilderOption { - return func(b *TxBuilder) { - b.feeCapMultiplier = multiplier - } -} - -// TxBuilder helps construct Ethereum transactions -type TxBuilder struct { - ctx context.Context - chain Chain - supportedTxTypes []uint8 - forcedTxType *uint8 // indicates if the tx type was manually set - gasLimitMarginPercent uint64 - feeCapMultiplier uint64 -} - -// NewTxBuilder creates a new transaction builder -func NewTxBuilder(ctx context.Context, chain Chain, opts ...TxBuilderOption) *TxBuilder { - builder := &TxBuilder{ - chain: chain, - ctx: ctx, - supportedTxTypes: []uint8{types.LegacyTxType}, // Legacy is always supported - gasLimitMarginPercent: DefaultGasLimitMarginPercent, - feeCapMultiplier: DefaultFeeCapMultiplier, - } - - // Apply any options provided - for _, opt := range opts { - opt(builder) - } - - // Skip network checks if tx type is forced - if builder.forcedTxType == nil { - if builder.chain.Nodes()[0].SupportsEIP(ctx, 1559) { - builder.supportedTxTypes = append(builder.supportedTxTypes, types.DynamicFeeTxType) - builder.supportedTxTypes = append(builder.supportedTxTypes, types.AccessListTxType) - } - if builder.chain.Nodes()[0].SupportsEIP(ctx, 4844) { - builder.supportedTxTypes = append(builder.supportedTxTypes, types.BlobTxType) - } - } - - log.Info("Instantiated TxBuilder", - "supportedTxTypes", builder.supportedTxTypes, - "forcedTxType", builder.forcedTxType, - "gasLimitMargin", builder.gasLimitMarginPercent, - "feeCapMultiplier", builder.feeCapMultiplier, - ) - return builder -} - -// BuildTx creates a new transaction, using the appropriate type for the network -func (b *TxBuilder) BuildTx(options ...TxOption) (Transaction, error) { - // Apply options to create TxOpts - opts := &TxOpts{} - for _, opt := range options { - opt(opts) - } - - // Check for blob transaction requirements if blobs are provided - if len(opts.blobHashes) > 0 { - if b.forcedTxType != nil && *b.forcedTxType != types.BlobTxType { - return nil, fmt.Errorf("blob transactions not supported with forced transaction type %d", *b.forcedTxType) - } - if !b.supportsType(types.BlobTxType) { - return nil, fmt.Errorf("blob transactions not supported by the network") - } - } - - // Validate all fields - if err := opts.Validate(); err != nil { - return nil, fmt.Errorf("invalid transaction options: %w", err) - } - - var tx *types.Transaction - var err error - - // Choose the most advanced supported transaction type - txType := b.chooseTxType(len(opts.accessList) > 0, len(opts.blobHashes) > 0) - switch txType { - case types.BlobTxType: - if len(opts.blobHashes) > 0 { - tx, err = b.buildBlobTx(opts) - } else { - // If blob tx type is forced but no blobs provided, fall back to dynamic fee tx - tx, err = b.buildDynamicFeeTx(opts) - } - case types.AccessListTxType: - tx, err = b.buildAccessListTx(opts) - case types.DynamicFeeTxType: - tx, err = b.buildDynamicFeeTx(opts) - default: - tx, err = b.buildLegacyTx(opts) - } - - if err != nil { - return nil, err - } - - return &EthTx{ - tx: tx, - from: opts.from, - txType: txType, - }, nil -} - -// supportsType checks if a transaction type is supported -func (b *TxBuilder) supportsType(txType uint8) bool { - for _, t := range b.supportedTxTypes { - if t == txType { - return true - } - } - return false -} - -// chooseTxType selects the most advanced supported transaction type -func (b *TxBuilder) chooseTxType(hasAccessList bool, hasBlobs bool) uint8 { - if b.forcedTxType != nil { - return *b.forcedTxType - } - - // Blob transactions are the most advanced, but only use them if we have blobs - if hasBlobs && b.supportsType(types.BlobTxType) { - return types.BlobTxType - } - - // If we have an access list and support access list transactions, use that - if hasAccessList && b.supportsType(types.AccessListTxType) { - return types.AccessListTxType - } - - // Try dynamic fee transactions next - if b.supportsType(types.DynamicFeeTxType) { - return types.DynamicFeeTxType - } - - // Fall back to legacy - return types.LegacyTxType -} - -// getNonce gets the next nonce for the given address -func (b *TxBuilder) getNonce(from common.Address) (uint64, error) { - nonce, err := b.chain.Nodes()[0].PendingNonceAt(b.ctx, from) - if err != nil { - return 0, fmt.Errorf("failed to get nonce: %w", err) - } - return nonce, nil -} - -// getGasPrice gets the suggested gas price from the network -func (b *TxBuilder) getGasPrice() (*big.Int, error) { - gasPrice, err := b.chain.Nodes()[0].GasPrice(b.ctx) - if err != nil { - return nil, fmt.Errorf("failed to get gas price: %w", err) - } - return gasPrice, nil -} - -// calculateGasLimit calculates the gas limit for a transaction, with a configurable safety buffer -func (b *TxBuilder) calculateGasLimit(opts *TxOpts) (uint64, error) { - if opts.gasLimit != 0 { - return opts.gasLimit, nil - } - - estimated, err := b.chain.Nodes()[0].GasLimit(b.ctx, opts) - if err != nil { - return 0, fmt.Errorf("failed to estimate gas: %w", err) - } - // Add the configured margin to the estimated gas limit - return estimated * (100 + b.gasLimitMarginPercent) / 100, nil -} - -// buildDynamicFeeTx creates a new EIP-1559 transaction with the given parameters -func (b *TxBuilder) buildDynamicFeeTx(opts *TxOpts) (*types.Transaction, error) { - nonce, err := b.getNonce(opts.from) - if err != nil { - return nil, err - } - - gasPrice, err := b.getGasPrice() - if err != nil { - return nil, err - } - - chainID := b.chain.ID() - - gasLimit, err := b.calculateGasLimit(opts) - if err != nil { - return nil, err - } - - return types.NewTx(&types.DynamicFeeTx{ - ChainID: chainID, - Nonce: nonce, - GasTipCap: gasPrice, - GasFeeCap: new(big.Int).Mul(gasPrice, big.NewInt(int64(b.feeCapMultiplier))), - Gas: gasLimit, - To: opts.to, - Value: opts.value, - Data: opts.data, - }), nil -} - -// buildLegacyTx creates a new legacy (pre-EIP-1559) transaction -func (b *TxBuilder) buildLegacyTx(opts *TxOpts) (*types.Transaction, error) { - nonce, err := b.getNonce(opts.from) - if err != nil { - return nil, err - } - - gasPrice, err := b.getGasPrice() - if err != nil { - return nil, err - } - - gasLimit, err := b.calculateGasLimit(opts) - if err != nil { - return nil, err - } - - return types.NewTx(&types.LegacyTx{ - Nonce: nonce, - To: opts.to, - Value: opts.value, - Gas: gasLimit, - GasPrice: gasPrice, - Data: opts.data, - }), nil -} - -// buildAccessListTx creates a new EIP-2930 transaction with access list -func (b *TxBuilder) buildAccessListTx(opts *TxOpts) (*types.Transaction, error) { - nonce, err := b.getNonce(opts.from) - if err != nil { - return nil, err - } - - gasPrice, err := b.getGasPrice() - if err != nil { - return nil, err - } - - chainID := b.chain.ID() - - gasLimit, err := b.calculateGasLimit(opts) - if err != nil { - return nil, err - } - - return types.NewTx(&types.AccessListTx{ - ChainID: chainID, - Nonce: nonce, - GasPrice: gasPrice, - Gas: gasLimit, - To: opts.to, - Value: opts.value, - Data: opts.data, - AccessList: opts.accessList, - }), nil -} - -// buildBlobTx creates a new EIP-4844 blob transaction -func (b *TxBuilder) buildBlobTx(opts *TxOpts) (*types.Transaction, error) { - nonce, err := b.getNonce(opts.from) - if err != nil { - return nil, err - } - - gasPrice, err := b.getGasPrice() - if err != nil { - return nil, err - } - - chainID := b.chain.ID() - - gasLimit, err := b.calculateGasLimit(opts) - if err != nil { - return nil, err - } - - // Validate blob transaction requirements - if opts.to == nil { - return nil, fmt.Errorf("blob transactions must have a recipient") - } - - if len(opts.blobHashes) == 0 { - return nil, fmt.Errorf("blob transactions must have at least one blob hash") - } - - if len(opts.blobs) != len(opts.commitments) || len(opts.blobs) != len(opts.proofs) { - return nil, fmt.Errorf("mismatched number of blobs, commitments, and proofs") - } - - // Convert big.Int values to uint256.Int - chainIDU256, _ := uint256.FromBig(chainID) - gasTipCapU256, _ := uint256.FromBig(gasPrice) - gasFeeCapU256, _ := uint256.FromBig(new(big.Int).Mul(gasPrice, big.NewInt(int64(b.feeCapMultiplier)))) - valueU256, _ := uint256.FromBig(opts.value) - // For blob transactions, we'll use the same gas price for blob fee cap - blobFeeCapU256, _ := uint256.FromBig(gasPrice) - - return types.NewTx(&types.BlobTx{ - ChainID: chainIDU256, - Nonce: nonce, - GasTipCap: gasTipCapU256, - GasFeeCap: gasFeeCapU256, - Gas: gasLimit, - To: *opts.to, - Value: valueU256, - Data: opts.data, - AccessList: opts.accessList, - BlobFeeCap: blobFeeCapU256, - BlobHashes: opts.blobHashes, - Sidecar: &types.BlobTxSidecar{ - Blobs: opts.blobs, - Commitments: opts.commitments, - Proofs: opts.proofs, - }, - }), nil -} diff --git a/devnet-sdk/system/txbuilder_test.go b/devnet-sdk/system/txbuilder_test.go deleted file mode 100644 index f82a55537ae5e..0000000000000 --- a/devnet-sdk/system/txbuilder_test.go +++ /dev/null @@ -1,475 +0,0 @@ -package system - -import ( - "context" - "fmt" - "math/big" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/sources" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto/kzg4844" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/params" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - _ Chain = (*mockChain)(nil) - _ Node = (*mockNode)(nil) - _ Wallet = (*mockWallet)(nil) -) - -// mockWallet implements types.Wallet for testing -type mockWallet struct { - mock.Mock -} - -func (m *mockWallet) PrivateKey() types.Key { - args := m.Called() - return args.Get(0).(types.Key) -} - -func (m *mockWallet) Address() types.Address { - args := m.Called() - return args.Get(0).(common.Address) -} - -func (m *mockWallet) Send(ctx context.Context, tx Transaction) error { - return nil -} - -func (m *mockWallet) Sign(tx Transaction) (Transaction, error) { - return tx, nil -} - -func (m *mockWallet) SendETH(to types.Address, amount types.Balance) types.WriteInvocation[any] { - args := m.Called(to, amount) - return args.Get(0).(types.WriteInvocation[any]) -} - -func (m *mockWallet) InitiateMessage(chainID types.ChainID, target common.Address, message []byte) types.WriteInvocation[any] { - args := m.Called(chainID, target, message) - return args.Get(0).(types.WriteInvocation[any]) -} - -func (m *mockWallet) ExecuteMessage(identifier bindings.Identifier, sentMessage []byte) types.WriteInvocation[any] { - args := m.Called(identifier, sentMessage) - return args.Get(0).(types.WriteInvocation[any]) -} - -func (m *mockWallet) Balance() types.Balance { - args := m.Called() - return args.Get(0).(types.Balance) -} - -func (m *mockWallet) Nonce() uint64 { - args := m.Called() - return args.Get(0).(uint64) -} - -func (m *mockWallet) Transactor() *bind.TransactOpts { - return nil -} - -// mockChain implements the Chain interface for testing -type mockChain struct { - mock.Mock - wallet *mockWallet -} - -func newMockChain() *mockChain { - return &mockChain{ - wallet: new(mockWallet), - } -} - -func (m *mockChain) Nodes() []Node { - args := m.Called() - return args.Get(0).([]Node) -} - -func (m *mockChain) ID() types.ChainID { - args := m.Called() - return args.Get(0).(types.ChainID) -} - -func (m *mockChain) SupportsEIP(ctx context.Context, eip uint64) bool { - args := m.Called(ctx, eip) - return args.Bool(0) -} - -func (m *mockChain) ContractsRegistry() interfaces.ContractsRegistry { - args := m.Called() - return args.Get(0).(interfaces.ContractsRegistry) -} - -func (m *mockChain) RPCURL() string { - args := m.Called() - return args.String(0) -} - -func (m *mockChain) Client() (*sources.EthClient, error) { - args := m.Called() - return args.Get(0).(*sources.EthClient), nil -} - -func (m *mockChain) Wallets() WalletMap { - return nil -} - -func (m *mockChain) Config() (*params.ChainConfig, error) { - return nil, fmt.Errorf("not implemented for mock chain") -} - -func (m *mockChain) Addresses() AddressMap { - args := m.Called() - return args.Get(0).(AddressMap) -} - -type mockNode struct { - mock.Mock -} - -func newMockNode() *mockNode { - return &mockNode{} -} - -func (m *mockNode) GasPrice(ctx context.Context) (*big.Int, error) { - args := m.Called(ctx) - return args.Get(0).(*big.Int), args.Error(1) -} - -func (m *mockNode) GasLimit(ctx context.Context, tx TransactionData) (uint64, error) { - args := m.Called(ctx, tx) - return args.Get(0).(uint64), args.Error(1) -} - -func (m *mockNode) PendingNonceAt(ctx context.Context, addr common.Address) (uint64, error) { - args := m.Called(ctx, addr) - return args.Get(0).(uint64), args.Error(1) -} - -func (m *mockNode) BlockByNumber(ctx context.Context, number *big.Int) (eth.BlockInfo, error) { - args := m.Called(ctx, number) - return args.Get(0).(eth.BlockInfo), args.Error(1) -} - -func (m *mockNode) Client() (*sources.EthClient, error) { - args := m.Called() - return args.Get(0).(*sources.EthClient), args.Error(1) -} - -func (m *mockNode) ContractsRegistry() interfaces.ContractsRegistry { - args := m.Called() - return args.Get(0).(interfaces.ContractsRegistry) -} - -func (m *mockNode) GethClient() (*ethclient.Client, error) { - args := m.Called() - return args.Get(0).(*ethclient.Client), args.Error(1) -} - -func (m *mockNode) RPCURL() string { - args := m.Called() - return args.Get(0).(string) -} - -func (m *mockNode) SupportsEIP(ctx context.Context, eip uint64) bool { - args := m.Called(ctx, eip) - return args.Bool(0) -} - -func (m *mockNode) Name() string { - args := m.Called() - return args.String(0) -} - -func TestNewTxBuilder(t *testing.T) { - ctx := context.Background() - - var node *mockNode - var chain *mockChain - tests := []struct { - name string - setupMock func() - opts []TxBuilderOption - expectedTypes []uint8 - expectedMargin uint64 - }{ - { - name: "legacy only", - setupMock: func() { - chain = newMockChain() - node = newMockNode() - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(false).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() - }, - opts: nil, - expectedTypes: []uint8{ethtypes.LegacyTxType}, - expectedMargin: DefaultGasLimitMarginPercent, - }, - { - name: "with EIP-1559", - setupMock: func() { - chain = newMockChain() - node = newMockNode() - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(true).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() - }, - opts: nil, - expectedTypes: []uint8{ethtypes.LegacyTxType, ethtypes.DynamicFeeTxType, ethtypes.AccessListTxType}, - expectedMargin: DefaultGasLimitMarginPercent, - }, - { - name: "with EIP-4844", - setupMock: func() { - chain = newMockChain() - node = newMockNode() - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(true).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(true).Once() - }, - opts: nil, - expectedTypes: []uint8{ethtypes.LegacyTxType, ethtypes.DynamicFeeTxType, ethtypes.AccessListTxType, ethtypes.BlobTxType}, - expectedMargin: DefaultGasLimitMarginPercent, - }, - { - name: "forced tx type", - setupMock: func() { - // No EIP checks needed when type is forced - }, - opts: []TxBuilderOption{ - WithTxType(ethtypes.DynamicFeeTxType), - }, - expectedTypes: []uint8{ethtypes.DynamicFeeTxType}, - expectedMargin: DefaultGasLimitMarginPercent, - }, - { - name: "custom margin", - setupMock: func() { - chain = newMockChain() - node = newMockNode() - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(false).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() - }, - opts: []TxBuilderOption{ - WithGasLimitMargin(50), - }, - expectedTypes: []uint8{ethtypes.LegacyTxType}, - expectedMargin: 50, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.setupMock() - builder := NewTxBuilder(ctx, chain, tt.opts...) - - assert.Equal(t, tt.expectedTypes, builder.supportedTxTypes) - assert.Equal(t, tt.expectedMargin, builder.gasLimitMarginPercent) - chain.AssertExpectations(t) - }) - } -} - -func TestBuildTx(t *testing.T) { - ctx := context.Background() - chain := newMockChain() - node := newMockNode() - addr := common.HexToAddress("0x1234567890123456789012345678901234567890") - to := common.HexToAddress("0x0987654321098765432109876543210987654321") - chainID := big.NewInt(1) - gasPrice := big.NewInt(1000000000) // 1 gwei - nonce := uint64(1) - - tests := []struct { - name string - setupMock func() - opts []TxOption - wantType uint8 - wantErr bool - }{ - { - name: "legacy tx", - setupMock: func() { - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(false).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() - node.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() - node.On("GasPrice", ctx).Return(gasPrice, nil).Once() - node.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() - }, - opts: []TxOption{ - WithFrom(addr), - WithTo(to), - WithValue(big.NewInt(100000000000000000)), // 0.1 ETH - }, - wantType: ethtypes.LegacyTxType, - wantErr: false, - }, - { - name: "dynamic fee tx", - setupMock: func() { - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(true).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() - node.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() - node.On("GasPrice", ctx).Return(gasPrice, nil).Once() - chain.On("ID").Return(chainID).Once() - node.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() - }, - opts: []TxOption{ - WithFrom(addr), - WithTo(to), - WithValue(big.NewInt(100000000000000000)), // 0.1 ETH - }, - wantType: ethtypes.DynamicFeeTxType, - wantErr: false, - }, - { - name: "access list tx", - setupMock: func() { - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(true).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() - node.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() - node.On("GasPrice", ctx).Return(gasPrice, nil).Once() - chain.On("ID").Return(chainID).Once() - node.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() - }, - opts: []TxOption{ - WithFrom(addr), - WithTo(to), - WithValue(big.NewInt(100000000000000000)), // 0.1 ETH - WithAccessList(ethtypes.AccessList{ - { - Address: common.HexToAddress("0x1234567890123456789012345678901234567890"), - StorageKeys: []common.Hash{ - common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"), - }, - }, - }), - }, - wantType: ethtypes.AccessListTxType, - wantErr: false, - }, - { - name: "blob tx", - setupMock: func() { - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(true).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(true).Once() - node.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() - node.On("GasPrice", ctx).Return(gasPrice, nil).Once() - chain.On("ID").Return(chainID).Once() - node.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() - }, - opts: []TxOption{ - WithFrom(addr), - WithTo(to), - WithValue(big.NewInt(100000000000000000)), // 0.1 ETH - WithBlobs([]kzg4844.Blob{{}}), - WithBlobCommitments([]kzg4844.Commitment{{}}), - WithBlobProofs([]kzg4844.Proof{{}}), - WithBlobHashes([]common.Hash{{}}), - }, - wantType: ethtypes.BlobTxType, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.setupMock() - builder := NewTxBuilder(ctx, chain) - tx, err := builder.BuildTx(tt.opts...) - - if tt.wantErr { - assert.Error(t, err) - return - } - - assert.NoError(t, err) - assert.Equal(t, tt.wantType, tx.Type()) - chain.AssertExpectations(t) - }) - } -} - -func TestCalculateGasLimit(t *testing.T) { - ctx := context.Background() - addr := common.HexToAddress("0x1234567890123456789012345678901234567890") - - tests := []struct { - name string - opts *TxOpts - margin uint64 - estimatedGas uint64 - expectedLimit uint64 - expectEstimate bool - wantErr bool - }{ - { - name: "explicit gas limit", - opts: &TxOpts{ - from: addr, - to: &addr, - value: big.NewInt(0), - gasLimit: 21000, - }, - margin: 20, - estimatedGas: 0, - expectedLimit: 21000, - expectEstimate: false, - wantErr: false, - }, - { - name: "estimated with margin", - opts: &TxOpts{ - from: addr, - to: &addr, - value: big.NewInt(0), - }, - margin: 20, - estimatedGas: 21000, - expectedLimit: 25200, // 21000 * 1.2 - expectEstimate: true, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Set up EIP support expectations for NewTxBuilder - chain := newMockChain() - node := newMockNode() - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(false) - node.On("SupportsEIP", ctx, uint64(4844)).Return(false) - node.On("GasLimit", ctx, tt.opts).Return(tt.estimatedGas, nil).Once() - - builder := NewTxBuilder(ctx, chain, WithGasLimitMargin(tt.margin)) - limit, err := builder.calculateGasLimit(tt.opts) - - if tt.wantErr { - assert.Error(t, err) - return - } - - assert.NoError(t, err) - assert.Equal(t, tt.expectedLimit, limit) - chain.AssertExpectations(t) - }) - } -} diff --git a/devnet-sdk/system/txprocessor.go b/devnet-sdk/system/txprocessor.go deleted file mode 100644 index 1279aadffc1b6..0000000000000 --- a/devnet-sdk/system/txprocessor.go +++ /dev/null @@ -1,83 +0,0 @@ -package system - -import ( - "context" - "fmt" - "math/big" - - sdkTypes "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" -) - -// EthClient defines the interface for interacting with Ethereum node -type EthClient interface { - SendTransaction(ctx context.Context, tx *types.Transaction) error -} - -// TransactionProcessor handles signing and sending transactions -type transactionProcessor struct { - client EthClient - chainID *big.Int - privateKey sdkTypes.Key -} - -// NewTransactionProcessor creates a new transaction processor -func NewTransactionProcessor(client EthClient, chainID *big.Int) TransactionProcessor { - return &transactionProcessor{ - client: client, - chainID: chainID, - } -} - -// NewEthTransactionProcessor creates a new transaction processor with an ethclient -func NewEthTransactionProcessor(client *ethclient.Client, chainID *big.Int) TransactionProcessor { - return NewTransactionProcessor(client, chainID) -} - -// Sign signs a transaction with the given private key -func (p *transactionProcessor) Sign(tx Transaction) (Transaction, error) { - pk := p.privateKey - if pk == nil { - return nil, fmt.Errorf("private key is nil") - } - - var signer types.Signer - switch tx.Type() { - case types.SetCodeTxType: - signer = types.NewIsthmusSigner(p.chainID) - case types.DynamicFeeTxType: - signer = types.NewLondonSigner(p.chainID) - case types.AccessListTxType: - signer = types.NewEIP2930Signer(p.chainID) - default: - signer = types.NewEIP155Signer(p.chainID) - } - - if rt, ok := tx.(RawTransaction); ok { - signedTx, err := types.SignTx(rt.Raw(), signer, pk) - if err != nil { - return nil, fmt.Errorf("failed to sign transaction: %w", err) - } - - return &EthTx{ - tx: signedTx, - from: tx.From(), - txType: tx.Type(), - }, nil - } - - return nil, fmt.Errorf("transaction does not support signing") -} - -// Send sends a signed transaction to the network -func (p *transactionProcessor) Send(ctx context.Context, tx Transaction) error { - if st, ok := tx.(RawTransaction); ok { - if err := p.client.SendTransaction(ctx, st.Raw()); err != nil { - return fmt.Errorf("failed to send transaction: %w", err) - } - return nil - } - - return fmt.Errorf("transaction is not signed") -} diff --git a/devnet-sdk/system/txprocessor_test.go b/devnet-sdk/system/txprocessor_test.go deleted file mode 100644 index c69776d53a7cf..0000000000000 --- a/devnet-sdk/system/txprocessor_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package system - -import ( - "context" - "fmt" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/stretchr/testify/assert" -) - -func (m *mockEthClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { - args := m.Called(ctx, tx) - return args.Error(0) -} - -func TestTransactionProcessor_Sign(t *testing.T) { - // Test private key and corresponding address - // DO NOT use this key for anything other than testing - testKey := "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" - testAddr := common.HexToAddress("0x96216849c49358B10257cb55b28eA603c874b05E") - - chainID := big.NewInt(1) - client := new(mockEthClient) - - // Create a wallet with the test key - chain := newChain(chainID.String(), WalletMap{}, nil, AddressMap{}, []Node{}) - wallet, err := NewWallet(testKey, testAddr, chain) - assert.NoError(t, err) - - processor := &transactionProcessor{ - client: client, - chainID: chainID, - privateKey: wallet.PrivateKey(), - } - - invalidProcessor := &transactionProcessor{ - client: client, - chainID: chainID, - // No private key set - } - - tests := []struct { - name string - processor *transactionProcessor - tx Transaction - wantType uint8 - wantErr bool - errMessage string - }{ - { - name: "legacy tx", - processor: processor, - tx: &EthTx{ - tx: types.NewTransaction( - 0, - testAddr, - big.NewInt(1), - 21000, - big.NewInt(1), - nil, - ), - from: testAddr, - txType: types.LegacyTxType, - }, - wantType: types.LegacyTxType, - wantErr: false, - }, - { - name: "dynamic fee tx", - processor: processor, - tx: &EthTx{ - tx: types.NewTx(&types.DynamicFeeTx{ - ChainID: chainID, - Nonce: 0, - GasTipCap: big.NewInt(1), - GasFeeCap: big.NewInt(1), - Gas: 21000, - To: &testAddr, - Value: big.NewInt(1), - Data: nil, - }), - from: testAddr, - txType: types.DynamicFeeTxType, - }, - wantType: types.DynamicFeeTxType, - wantErr: false, - }, - { - name: "invalid private key", - processor: invalidProcessor, - tx: &EthTx{ - tx: types.NewTransaction( - 0, - testAddr, - big.NewInt(1), - 21000, - big.NewInt(1), - nil, - ), - from: testAddr, - txType: types.LegacyTxType, - }, - wantErr: true, - errMessage: "private key is nil", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - signedTx, err := tt.processor.Sign(tt.tx) - if tt.wantErr { - assert.Error(t, err) - assert.Contains(t, err.Error(), tt.errMessage) - return - } - - assert.NoError(t, err) - assert.NotNil(t, signedTx) - assert.Equal(t, tt.wantType, signedTx.Type()) - assert.Equal(t, tt.tx.From(), signedTx.From()) - }) - } -} - -func TestTransactionProcessor_Send(t *testing.T) { - chainID := big.NewInt(1) - client := new(mockEthClient) - processor := NewTransactionProcessor(client, chainID) - ctx := context.Background() - - testAddr := common.HexToAddress("0x1234567890123456789012345678901234567890") - tx := types.NewTransaction( - 0, - testAddr, - big.NewInt(1), - 21000, - big.NewInt(1), - nil, - ) - - tests := []struct { - name string - tx Transaction - setupMock func() - wantErr bool - errMessage string - }{ - { - name: "successful send", - tx: &EthTx{ - tx: tx, - from: testAddr, - txType: types.LegacyTxType, - }, - setupMock: func() { - client.On("SendTransaction", ctx, tx).Return(nil).Once() - }, - wantErr: false, - }, - { - name: "send error", - tx: &EthTx{ - tx: tx, - from: testAddr, - txType: types.LegacyTxType, - }, - setupMock: func() { - client.On("SendTransaction", ctx, tx).Return(fmt.Errorf("send failed")).Once() - }, - wantErr: true, - errMessage: "failed to send transaction", - }, - { - name: "not a raw transaction", - tx: &mockTransaction{ - from: testAddr, - }, - setupMock: func() {}, - wantErr: true, - errMessage: "transaction is not signed", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.setupMock() - - err := processor.Send(ctx, tt.tx) - if tt.wantErr { - assert.Error(t, err) - assert.Contains(t, err.Error(), tt.errMessage) - return - } - - assert.NoError(t, err) - client.AssertExpectations(t) - }) - } -} - -// mockTransaction implements Transaction for testing -type mockTransaction struct { - from common.Address -} - -func (m *mockTransaction) Hash() common.Hash { return common.Hash{} } -func (m *mockTransaction) From() common.Address { return m.from } -func (m *mockTransaction) To() *common.Address { return nil } -func (m *mockTransaction) Value() *big.Int { return nil } -func (m *mockTransaction) Data() []byte { return nil } -func (m *mockTransaction) AccessList() types.AccessList { return nil } -func (m *mockTransaction) Type() uint8 { return 0 } diff --git a/devnet-sdk/system/wallet.go b/devnet-sdk/system/wallet.go deleted file mode 100644 index 7d5d14757e249..0000000000000 --- a/devnet-sdk/system/wallet.go +++ /dev/null @@ -1,422 +0,0 @@ -package system - -import ( - "context" - "encoding/hex" - "fmt" - "math/big" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" - "github.com/ethereum-optimism/optimism/op-service/eth" - supervisorTypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - - "github.com/ethereum-optimism/optimism/op-service/bigs" - coreTypes "github.com/ethereum/go-ethereum/core/types" -) - -var ( - // This will make sure that we implement the Chain interface - _ Wallet = (*wallet)(nil) -) - -type wallet struct { - privateKey types.Key - address types.Address - chain Chain -} - -func newWalletMapFromDescriptorWalletMap(descriptorWalletMap descriptors.WalletMap, chain Chain) (WalletMap, error) { - result := WalletMap{} - for k, v := range descriptorWalletMap { - wallet, err := NewWallet(v.PrivateKey, v.Address, chain) - if err != nil { - return nil, err - } - result[k] = wallet - } - return result, nil -} - -func NewWallet(pk string, addr types.Address, chain Chain) (*wallet, error) { - privateKey, err := privateKeyFromString(pk) - if err != nil { - return nil, fmt.Errorf("failed to convert private from string: %w", err) - } - - return &wallet{ - privateKey: privateKey, - address: addr, - chain: chain, - }, nil -} - -func privateKeyFromString(pk string) (types.Key, error) { - var privateKey types.Key - if pk != "" { - pk = strings.TrimPrefix(pk, "0x") - if len(pk)%2 == 1 { - pk = "0" + pk - } - pkBytes, err := hex.DecodeString(pk) - if err != nil { - return nil, fmt.Errorf("failed to decode private key: %w", err) - } - key, err := crypto.ToECDSA(pkBytes) - if err != nil { - return nil, fmt.Errorf("failed to convert private key to ECDSA: %w", err) - } - privateKey = key - } - - return privateKey, nil -} - -func (w *wallet) PrivateKey() types.Key { - return w.privateKey -} - -func (w *wallet) Address() types.Address { - return w.address -} - -func (w *wallet) SendETH(to types.Address, amount types.Balance) types.WriteInvocation[any] { - return &sendImpl{ - chain: w.chain, - processor: w, - from: w.address, - to: to, - amount: amount, - } -} - -func (w *wallet) Balance() types.Balance { - client, err := w.chain.Nodes()[0].Client() - if err != nil { - return types.Balance{} - } - - balance, err := client.BalanceAt(context.Background(), w.address, nil) - if err != nil { - return types.Balance{} - } - - return types.NewBalance(balance) -} - -func (w *wallet) InitiateMessage(chainID types.ChainID, target common.Address, message []byte) types.WriteInvocation[any] { - return &initiateMessageImpl{ - chain: w.chain, - processor: w, - from: w.address, - target: target, - chainID: chainID, - message: message, - } -} - -func (w *wallet) ExecuteMessage(identifier bindings.Identifier, sentMessage []byte) types.WriteInvocation[any] { - return &executeMessageImpl{ - chain: w.chain, - processor: w, - from: w.address, - identifier: identifier, - sentMessage: sentMessage, - } -} - -type initiateMessageImpl struct { - chain Chain - processor TransactionProcessor - from types.Address - - target types.Address - chainID types.ChainID - message []byte -} - -func (i *initiateMessageImpl) Call(ctx context.Context) (any, error) { - builder := NewTxBuilder(ctx, i.chain) - messenger, err := i.chain.Nodes()[0].ContractsRegistry().L2ToL2CrossDomainMessenger(constants.L2ToL2CrossDomainMessenger) - if err != nil { - return nil, fmt.Errorf("failed to init transaction: %w", err) - } - data, err := messenger.ABI().Pack("sendMessage", i.chainID, i.target, i.message) - if err != nil { - return nil, fmt.Errorf("failed to build calldata: %w", err) - } - tx, err := builder.BuildTx( - WithFrom(i.from), - WithTo(constants.L2ToL2CrossDomainMessenger), - WithValue(big.NewInt(0)), - WithData(data), - ) - if err != nil { - return nil, fmt.Errorf("failed to build transaction: %w", err) - } - - tx, err = i.processor.Sign(tx) - if err != nil { - return nil, fmt.Errorf("failed to sign transaction: %w", err) - } - - return tx, nil -} - -func (i *initiateMessageImpl) Send(ctx context.Context) types.InvocationResult { - result, err := i.Call(ctx) - if err != nil { - return &sendResult{chain: i.chain, tx: nil, err: err} - } - tx, ok := result.(Transaction) - if !ok { - return &sendResult{chain: i.chain, tx: nil, err: fmt.Errorf("unexpected return type")} - } - err = i.processor.Send(ctx, tx) - return &sendResult{ - chain: i.chain, - tx: tx, - err: err, - } -} - -type executeMessageImpl struct { - chain Chain - processor TransactionProcessor - from types.Address - - identifier bindings.Identifier - sentMessage []byte -} - -func (i *executeMessageImpl) Call(ctx context.Context) (any, error) { - builder := NewTxBuilder(ctx, i.chain) - messenger, err := i.chain.Nodes()[0].ContractsRegistry().L2ToL2CrossDomainMessenger(constants.L2ToL2CrossDomainMessenger) - if err != nil { - return nil, fmt.Errorf("failed to init transaction: %w", err) - } - data, err := messenger.ABI().Pack("relayMessage", i.identifier, i.sentMessage) - if err != nil { - return nil, fmt.Errorf("failed to build calldata: %w", err) - } - // Wrapper to use Access implementation - msg := supervisorTypes.Message{ - Identifier: supervisorTypes.Identifier{ - Origin: i.identifier.Origin, - BlockNumber: bigs.Uint64Strict(i.identifier.BlockNumber), - LogIndex: uint32(bigs.Uint64Strict(i.identifier.LogIndex)), - Timestamp: bigs.Uint64Strict(i.identifier.Timestamp), - ChainID: eth.ChainIDFromBig(i.identifier.ChainId), - }, - PayloadHash: crypto.Keccak256Hash(i.sentMessage), - } - access := msg.Access() - accessList := coreTypes.AccessList{{ - Address: constants.CrossL2Inbox, - StorageKeys: supervisorTypes.EncodeAccessList([]supervisorTypes.Access{access}), - }} - tx, err := builder.BuildTx( - WithFrom(i.from), - WithTo(constants.L2ToL2CrossDomainMessenger), - WithValue(big.NewInt(0)), - WithData(data), - WithAccessList(accessList), - ) - if err != nil { - return nil, fmt.Errorf("failed to build transaction: %w", err) - } - tx, err = i.processor.Sign(tx) - if err != nil { - return nil, fmt.Errorf("failed to sign transaction: %w", err) - } - return tx, nil -} - -func (i *executeMessageImpl) Send(ctx context.Context) types.InvocationResult { - result, err := i.Call(ctx) - if err != nil { - return &sendResult{chain: i.chain, tx: nil, err: err} - } - tx, ok := result.(Transaction) - if !ok { - return &sendResult{chain: i.chain, tx: nil, err: fmt.Errorf("unexpected return type")} - } - err = i.processor.Send(ctx, tx) - return &sendResult{ - chain: i.chain, - tx: tx, - err: err, - } -} - -func (w *wallet) Nonce() uint64 { - client, err := w.chain.Nodes()[0].Client() - if err != nil { - return 0 - } - - nonce, err := client.PendingNonceAt(context.Background(), w.address) - if err != nil { - return 0 - } - - return nonce -} - -func (w *wallet) Transactor() *bind.TransactOpts { - transactor, err := bind.NewKeyedTransactorWithChainID(w.PrivateKey(), w.chain.ID()) - if err != nil { - panic(fmt.Sprintf("could not create transactor for address %s and chainID %v", w.Address(), w.chain.ID())) - } - - return transactor -} - -func (w *wallet) Sign(tx Transaction) (Transaction, error) { - pk := w.privateKey - - var signer coreTypes.Signer - switch tx.Type() { - case coreTypes.SetCodeTxType: - signer = coreTypes.NewIsthmusSigner(w.chain.ID()) - case coreTypes.DynamicFeeTxType: - signer = coreTypes.NewLondonSigner(w.chain.ID()) - case coreTypes.AccessListTxType: - signer = coreTypes.NewEIP2930Signer(w.chain.ID()) - default: - signer = coreTypes.NewEIP155Signer(w.chain.ID()) - } - - if rt, ok := tx.(RawTransaction); ok { - signedTx, err := coreTypes.SignTx(rt.Raw(), signer, pk) - if err != nil { - return nil, fmt.Errorf("failed to sign transaction: %w", err) - } - - return &EthTx{ - tx: signedTx, - from: tx.From(), - txType: tx.Type(), - }, nil - } - - return nil, fmt.Errorf("transaction does not support signing") -} - -func (w *wallet) Send(ctx context.Context, tx Transaction) error { - if st, ok := tx.(RawTransaction); ok { - client, err := w.chain.Nodes()[0].Client() - if err != nil { - return fmt.Errorf("failed to get client: %w", err) - } - if err := client.SendTransaction(ctx, st.Raw()); err != nil { - return fmt.Errorf("failed to send transaction: %w", err) - } - return nil - } - - return fmt.Errorf("transaction is not signed") -} - -type sendImpl struct { - chain Chain - processor TransactionProcessor - from types.Address - to types.Address - amount types.Balance -} - -func (i *sendImpl) Call(ctx context.Context) (any, error) { - builder := NewTxBuilder(ctx, i.chain) - tx, err := builder.BuildTx( - WithFrom(i.from), - WithTo(i.to), - WithValue(i.amount.Int), - WithData(nil), - ) - if err != nil { - return nil, fmt.Errorf("failed to build transaction: %w", err) - } - - tx, err = i.processor.Sign(tx) - if err != nil { - return nil, fmt.Errorf("failed to sign transaction: %w", err) - } - - return tx, nil -} - -func (i *sendImpl) Send(ctx context.Context) types.InvocationResult { - builder := NewTxBuilder(ctx, i.chain) - tx, err := builder.BuildTx( - WithFrom(i.from), - WithTo(i.to), - WithValue(i.amount.Int), - WithData(nil), - ) - - // Sign the transaction if it's built okay - if err == nil { - tx, err = i.processor.Sign(tx) - } - - // Send the transaction if it's signed okay - if err == nil { - err = i.processor.Send(ctx, tx) - } - - return &sendResult{ - chain: i.chain, - tx: tx, - err: err, - } -} - -type sendResult struct { - chain Chain - tx Transaction - receipt Receipt - err error -} - -func (r *sendResult) Error() error { - return r.err -} - -func (r *sendResult) Wait() error { - client, err := r.chain.Nodes()[0].GethClient() - if err != nil { - return fmt.Errorf("failed to get client: %w", err) - } - - if r.err != nil { - return r.err - } - if r.tx == nil { - return fmt.Errorf("no transaction to wait for") - } - - if tx, ok := r.tx.(RawTransaction); ok { - receipt, err := wait.ForReceiptOK(context.Background(), client, tx.Raw().Hash()) - if err != nil { - return fmt.Errorf("failed waiting for transaction confirmation: %w", err) - } - r.receipt = &EthReceipt{blockNumber: receipt.BlockNumber, logs: receipt.Logs, txHash: receipt.TxHash} - if receipt.Status == 0 { - return fmt.Errorf("transaction failed") - } - } - - return nil -} - -func (r *sendResult) Info() any { - return r.receipt -} diff --git a/devnet-sdk/system/walletV2.go b/devnet-sdk/system/walletV2.go deleted file mode 100644 index 9e2d38c208861..0000000000000 --- a/devnet-sdk/system/walletV2.go +++ /dev/null @@ -1,93 +0,0 @@ -package system - -import ( - "context" - "crypto/ecdsa" - "fmt" - - "github.com/ethereum-optimism/optimism/op-service/client" - "github.com/ethereum-optimism/optimism/op-service/sources" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" -) - -var ( - _ WalletV2 = (*walletV2)(nil) -) - -type walletV2 struct { - address common.Address - priv *ecdsa.PrivateKey - client *sources.EthClient - gethClient *ethclient.Client - ctx context.Context -} - -func NewWalletV2FromWalletAndChain(ctx context.Context, wallet Wallet, chain Chain) (WalletV2, error) { - if len(chain.Nodes()) == 0 { - return nil, fmt.Errorf("failed to init walletV2: chain has zero nodes") - } - client, err := chain.Nodes()[0].Client() - if err != nil { - return nil, err - } - gethClient, err := chain.Nodes()[0].GethClient() - if err != nil { - return nil, err - } - return &walletV2{ - address: wallet.Address(), - priv: wallet.PrivateKey(), - client: client, - gethClient: gethClient, - ctx: ctx, - }, nil -} - -func NewWalletV2(ctx context.Context, rpcURL string, priv *ecdsa.PrivateKey, clCfg *sources.EthClientConfig, log log.Logger) (*walletV2, error) { - if clCfg == nil { - clCfg = sources.DefaultEthClientConfig(10) - } - rpcClient, err := rpc.DialContext(ctx, rpcURL) - if err != nil { - return nil, err - } - cl, err := sources.NewEthClient(client.NewBaseRPCClient(rpcClient), log, nil, clCfg) - if err != nil { - return nil, err - } - pubkeyECDSA, ok := priv.Public().(*ecdsa.PublicKey) - if !ok { - return nil, fmt.Errorf("Failed to assert type: publicKey is not of type *ecdsa.PublicKey") - } - address := crypto.PubkeyToAddress(*pubkeyECDSA) - return &walletV2{ - address: address, - client: cl, - priv: priv, - ctx: ctx, - }, nil -} - -func (w *walletV2) PrivateKey() *ecdsa.PrivateKey { - return w.priv -} - -func (w *walletV2) Client() *sources.EthClient { - return w.client -} - -func (w *walletV2) Ctx() context.Context { - return w.ctx -} - -func (w *walletV2) Address() common.Address { - return w.address -} - -func (w *walletV2) GethClient() *ethclient.Client { - return w.gethClient -} diff --git a/devnet-sdk/system/wallet_test.go b/devnet-sdk/system/wallet_test.go deleted file mode 100644 index 294869af1a6d5..0000000000000 --- a/devnet-sdk/system/wallet_test.go +++ /dev/null @@ -1,237 +0,0 @@ -package system - -import ( - "context" - "math/big" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum-optimism/optimism/op-service/client" - "github.com/ethereum-optimism/optimism/op-service/sources" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -// testWallet is a minimal wallet implementation for testing balance functionality -type testWallet struct { - privateKey types.Key - address types.Address - chain *mockChainForBalance // Use concrete type to access mock client directly -} - -func (w *testWallet) Balance() types.Balance { - // Use the mock client directly instead of going through Client() - balance, err := w.chain.client.BalanceAt(context.Background(), w.address, nil) - if err != nil { - return types.NewBalance(new(big.Int)) - } - - return types.NewBalance(balance) -} - -// mockEthClient implements a mock ethereum client for testing -type mockEthClient struct { - mock.Mock -} - -func (m *mockEthClient) BalanceAt(ctx context.Context, account types.Address, blockNumber *big.Int) (*big.Int, error) { - args := m.Called(ctx, account, blockNumber) - if args.Get(0) == nil { - return nil, args.Error(1) - } - return args.Get(0).(*big.Int), args.Error(1) -} - -func (m *mockEthClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { - args := m.Called(ctx, account) - return args.Get(0).(uint64), args.Error(1) -} - -// mockChainForBalance implements just enough of the chain interface for balance testing -type mockChainForBalance struct { - mock.Mock - client *mockEthClient -} - -func TestWalletBalance(t *testing.T) { - tests := []struct { - name string - setupMock func(*mockChainForBalance) - expectedValue *big.Int - }{ - { - name: "successful balance fetch", - setupMock: func(m *mockChainForBalance) { - balance := big.NewInt(1000000000000000000) // 1 ETH - m.client.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(balance, nil) - }, - expectedValue: big.NewInt(1000000000000000000), - }, - { - name: "balance fetch error returns zero", - setupMock: func(m *mockChainForBalance) { - m.client.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(nil, assert.AnError) - }, - expectedValue: new(big.Int), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockChain := &mockChainForBalance{ - client: new(mockEthClient), - } - tt.setupMock(mockChain) - - w := &testWallet{ - privateKey: crypto.ToECDSAUnsafe(common.FromHex("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")), - address: types.Address{}, - chain: mockChain, - } - - balance := w.Balance() - assert.Equal(t, 0, balance.Int.Cmp(tt.expectedValue)) - - mockChain.AssertExpectations(t) - mockChain.client.AssertExpectations(t) - }) - } -} - -type internalMockChain struct { - *mockChain -} - -func (m *internalMockChain) Client() (*sources.EthClient, error) { - args := m.Called() - if args.Get(0) == nil { - return nil, args.Error(1) - } - return args.Get(0).(*sources.EthClient), args.Error(1) -} - -func (m *internalMockChain) GethClient() (*ethclient.Client, error) { - args := m.Called() - if args.Get(0) == nil { - return nil, args.Error(1) - } - return args.Get(0).(*ethclient.Client), args.Error(1) -} - -func TestNewWallet(t *testing.T) { - pk := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" - addr := types.Address(common.HexToAddress("0x5678")) - chain := &chain{} - - w, err := NewWallet(pk, addr, chain) - assert.NoError(t, err) - - // The private key is converted to ECDSA, so we can't compare directly with the input string - assert.NotNil(t, w.privateKey) - assert.Equal(t, addr, w.address) - assert.Equal(t, chain, w.chain) -} - -func TestWallet_Address(t *testing.T) { - addr := types.Address(common.HexToAddress("0x5678")) - w := &wallet{address: addr} - - assert.Equal(t, addr, w.Address()) -} - -func TestWallet_SendETH(t *testing.T) { - ctx := context.Background() - mockChain := newMockChain() - mockNode := newMockNode() - internalChain := &internalMockChain{mockChain} - - // Use a valid 256-bit private key (32 bytes) - testPrivateKey := crypto.ToECDSAUnsafe(common.FromHex("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")) - - // Derive the address from the private key - fromAddr := crypto.PubkeyToAddress(testPrivateKey.PublicKey) - - w := &wallet{ - privateKey: testPrivateKey, - address: types.Address(fromAddr), - chain: internalChain, - } - - toAddr := types.Address(common.HexToAddress("0x5678")) - amount := types.NewBalance(big.NewInt(1000000)) - - chainID := big.NewInt(1) - - // Mock chain ID for all calls - mockChain.On("ID").Return(types.ChainID(chainID)).Maybe() - - // Mock EIP support checks - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - mockNode.On("SupportsEIP", ctx, uint64(1559)).Return(false) - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - mockNode.On("SupportsEIP", ctx, uint64(4844)).Return(false) - - // Mock gas price and limit - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - mockNode.On("GasPrice", ctx).Return(big.NewInt(1000000000), nil) - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - mockNode.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil) - - // Mock nonce retrieval - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - mockNode.On("PendingNonceAt", ctx, fromAddr).Return(uint64(0), nil) - - // Mock client access - rpcClient, err := rpc.DialContext(context.Background(), "http://this.domain.definitely.does.not.exist:8545") - assert.NoError(t, err) - ethClCfg := sources.EthClientConfig{MaxConcurrentRequests: 1, MaxRequestsPerBatch: 1, RPCProviderKind: sources.RPCKindStandard} - ethCl, err := sources.NewEthClient(client.NewBaseRPCClient(rpcClient), log.Root(), nil, ðClCfg) - assert.NoError(t, err) - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - mockNode.On("Client").Return(ethCl, nil) - - // Create the send invocation - invocation := w.SendETH(toAddr, amount) - assert.NotNil(t, invocation) - - // Send the transaction - result := invocation.Send(ctx) - assert.Error(t, result.Error()) // We expect an error since the client can't connect - - mockChain.AssertExpectations(t) -} - -func TestWallet_Balance(t *testing.T) { - mockChain := newMockChain() - mockNode := newMockNode() - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - internalChain := &internalMockChain{mockChain} - w := &wallet{ - chain: internalChain, - } - - // Test error case when client is not available - mockNode.On("Client").Return((*sources.EthClient)(nil), assert.AnError).Once() - balance := w.Balance() - assert.Equal(t, types.Balance{}, balance) -} - -func TestWallet_Nonce(t *testing.T) { - mockChain := newMockChain() - mockNode := newMockNode() - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - internalChain := &internalMockChain{mockChain} - w := &wallet{ - chain: internalChain, - } - - // Test error case when client is not available - mockNode.On("Client").Return((*sources.EthClient)(nil), assert.AnError).Once() - nonce := w.Nonce() - assert.Equal(t, uint64(0), nonce) -} diff --git a/devnet-sdk/telemetry/carrier.go b/devnet-sdk/telemetry/carrier.go deleted file mode 100644 index dfbbdcfbee5d6..0000000000000 --- a/devnet-sdk/telemetry/carrier.go +++ /dev/null @@ -1,55 +0,0 @@ -package telemetry - -import ( - "context" - "fmt" - "strings" - - "go.opentelemetry.io/otel/propagation" -) - -const CarrierEnvVarPrefix = "OTEL_DEVSTACK_PROPAGATOR_CARRIER_" - -// keep in sync with textPropagator() below -var defaultPropagators = []string{ - "tracecontext", - "baggage", -} - -func textPropagator() propagation.TextMapPropagator { - return propagation.NewCompositeTextMapPropagator( - // keep in sync with propagators above - propagation.TraceContext{}, - propagation.Baggage{}, - ) -} - -func InstrumentEnvironment(ctx context.Context, env []string) []string { - propagator := textPropagator() - carrier := propagation.MapCarrier{} - propagator.Inject(ctx, carrier) - - for k, v := range carrier { - env = append(env, fmt.Sprintf("%s%s=%s", CarrierEnvVarPrefix, k, v)) - } - - return env -} - -func ExtractEnvironment(ctx context.Context, env []string) (context.Context, error) { - carrier := propagation.MapCarrier{} - // Reconstruct the carrier from the environment variables - for _, e := range env { - if strings.HasPrefix(e, CarrierEnvVarPrefix) { - parts := strings.SplitN(e, "=", 2) - if len(parts) == 2 { - key := strings.TrimPrefix(parts[0], CarrierEnvVarPrefix) - value := parts[1] - carrier.Set(key, value) - } - } - } - - ctx = textPropagator().Extract(ctx, carrier) - return ctx, nil -} diff --git a/devnet-sdk/telemetry/init.go b/devnet-sdk/telemetry/init.go deleted file mode 100644 index 52ffe4bc1fa67..0000000000000 --- a/devnet-sdk/telemetry/init.go +++ /dev/null @@ -1,58 +0,0 @@ -package telemetry - -import ( - "context" - "os" - - "github.com/honeycombio/otel-config-go/otelconfig" -) - -const ( - serviceNameEnvVar = "OTEL_SERVICE_NAME" - serviceVersionEnvVar = "OTEL_SERVICE_VERSION" - tracesEndpointEnvVar = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT" - metricsEndpointEnvVar = "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT" - - defaultServiceName = "devstack" - defaultServiceVersion = "0.0.0" -) - -func envOrDefault(key, def string) string { - if v, ok := os.LookupEnv(key); ok { - return v - } - return def -} - -func SetupOpenTelemetry(ctx context.Context, opts ...otelconfig.Option) (context.Context, func(), error) { - defaultOpts := []otelconfig.Option{ - otelconfig.WithServiceName(envOrDefault(serviceNameEnvVar, defaultServiceName)), - otelconfig.WithServiceVersion(envOrDefault(serviceVersionEnvVar, defaultServiceVersion)), - otelconfig.WithPropagators(defaultPropagators), - } - - // do not use localhost:4317 by default, we want telemetry to be opt-in and - // explicit. - // The caller is still able to override this by passing in their own opts. - if os.Getenv(tracesEndpointEnvVar) == "" { - defaultOpts = append(defaultOpts, otelconfig.WithTracesEnabled(false)) - } - if os.Getenv(metricsEndpointEnvVar) == "" { - defaultOpts = append(defaultOpts, otelconfig.WithMetricsEnabled(false)) - } - - opts = append(defaultOpts, opts...) - otelShutdown, err := otelconfig.ConfigureOpenTelemetry(opts...) - if err != nil { - return ctx, nil, err - } - - // If the environment contains carrier information, extract it. - // This is useful for test runner / test communication for example. - ctx, err = ExtractEnvironment(ctx, os.Environ()) - if err != nil { - return ctx, nil, err - } - - return ctx, otelShutdown, nil -} diff --git a/devnet-sdk/telemetry/slog.go b/devnet-sdk/telemetry/slog.go deleted file mode 100644 index ce7429a053016..0000000000000 --- a/devnet-sdk/telemetry/slog.go +++ /dev/null @@ -1,98 +0,0 @@ -package telemetry - -import ( - "context" - "fmt" - "log/slog" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" - - "github.com/ethereum-optimism/optimism/op-service/logmods" -) - -func WrapHandler(h slog.Handler) slog.Handler { - return &tracingHandler{ - Handler: h, - } -} - -type tracingHandler struct { - slog.Handler -} - -var _ logmods.Handler = (*tracingHandler)(nil) - -func (h *tracingHandler) Unwrap() slog.Handler { - return h.Handler -} - -func (h *tracingHandler) WithAttrs(attrs []slog.Attr) slog.Handler { - return &tracingHandler{Handler: h.Handler.WithAttrs(attrs)} -} - -func (h *tracingHandler) WithGroup(name string) slog.Handler { - return &tracingHandler{Handler: h.Handler.WithGroup(name)} -} - -func (h *tracingHandler) Handle(ctx context.Context, record slog.Record) error { - // Send log entries as events to the tracer - span := trace.SpanFromContext(ctx) - if span.IsRecording() { - attrRecorder := &attrAccumulator{} - record.Attrs(func(a slog.Attr) bool { - attrRecorder.register(a) - return true - }) - span.AddEvent(record.Message, trace.WithAttributes(attrRecorder.kv...)) - } - - // Conversely add tracing data to the local logs - spanCtx := trace.SpanContextFromContext(ctx) - if spanCtx.HasTraceID() { - record.AddAttrs(slog.String("trace_id", spanCtx.TraceID().String())) - } - if spanCtx.HasSpanID() { - record.AddAttrs(slog.String("span_id", spanCtx.SpanID().String())) - } - return h.Handler.Handle(ctx, record) -} - -type attrAccumulator struct { - kv []attribute.KeyValue -} - -func (ac *attrAccumulator) register(a slog.Attr) { - switch a.Value.Kind() { - case slog.KindAny: - ac.kv = append(ac.kv, attribute.String(a.Key, fmt.Sprintf("%v", a.Value.Any()))) - case slog.KindBool: - ac.kv = append(ac.kv, attribute.Bool(a.Key, a.Value.Bool())) - case slog.KindDuration: - ac.kv = append(ac.kv, attribute.String(a.Key, a.Value.Duration().String())) - case slog.KindFloat64: - ac.kv = append(ac.kv, attribute.Float64(a.Key, a.Value.Float64())) - case slog.KindInt64: - ac.kv = append(ac.kv, attribute.Int64(a.Key, a.Value.Int64())) - case slog.KindString: - ac.kv = append(ac.kv, attribute.String(a.Key, a.Value.String())) - case slog.KindTime: - ac.kv = append(ac.kv, attribute.String(a.Key, a.Value.Time().String())) - case slog.KindUint64: - val := a.Value.Uint64() - ac.kv = append(ac.kv, attribute.Int64(a.Key, int64(val))) - // detect overflows - if val > uint64(1<<63-1) { - // Value doesn't properly fit in int64 - ac.kv = append(ac.kv, attribute.Bool(a.Key+".overflow", true)) - ac.kv = append(ac.kv, attribute.String(a.Key+".actual", fmt.Sprintf("%d", val))) - } - case slog.KindGroup: - for _, attr := range a.Value.Group() { - ac.register(attr) - } - case slog.KindLogValuer: - value := a.Value.LogValuer().LogValue() - ac.register(slog.Attr{Key: a.Key, Value: value}) - } -} diff --git a/devnet-sdk/types/balance.go b/devnet-sdk/types/balance.go deleted file mode 100644 index bc2e4aa5942ea..0000000000000 --- a/devnet-sdk/types/balance.go +++ /dev/null @@ -1,92 +0,0 @@ -package types - -import ( - "fmt" - "math/big" -) - -type Balance struct { - *big.Int -} - -// NewBalance creates a new Balance from a big.Int -func NewBalance(i *big.Int) Balance { - return Balance{Int: new(big.Int).Set(i)} -} - -// Add returns a new Balance with other added to it -func (b Balance) Add(other Balance) Balance { - return Balance{Int: new(big.Int).Add(b.Int, other.Int)} -} - -// Sub returns a new Balance with other subtracted from it -func (b Balance) Sub(other Balance) Balance { - return Balance{Int: new(big.Int).Sub(b.Int, other.Int)} -} - -// Mul returns a new Balance multiplied by a float64 -func (b Balance) Mul(f float64) Balance { - floatResult := new(big.Float).Mul(new(big.Float).SetInt(b.Int), new(big.Float).SetFloat64(f)) - result := new(big.Int) - floatResult.Int(result) - return Balance{Int: result} -} - -// GreaterThan returns true if this balance is greater than other -func (b Balance) GreaterThan(other Balance) bool { - if b.Int == nil { - return false - } - if other.Int == nil { - return true - } - return b.Int.Cmp(other.Int) > 0 -} - -// LessThan returns true if this balance is less than other -func (b Balance) LessThan(other Balance) bool { - if b.Int == nil { - return other.Int != nil - } - if other.Int == nil { - return false - } - return b.Int.Cmp(other.Int) < 0 -} - -// Equal returns true if this balance equals other -func (b Balance) Equal(other Balance) bool { - if b.Int == nil { - return other.Int == nil - } - if other.Int == nil { - return false - } - return b.Int.Cmp(other.Int) == 0 -} - -// String implements fmt.Stringer to format Balance in the most readable unit -func (b Balance) String() string { - if b.Int == nil { - return "0 ETH" - } - - val := new(big.Float).SetInt(b.Int) - eth := new(big.Float).Quo(val, new(big.Float).SetInt64(1e18)) - - // 1 ETH = 1e18 Wei - if eth.Cmp(new(big.Float).SetFloat64(0.001)) >= 0 { - str := eth.Text('f', 0) - return fmt.Sprintf("%s ETH", str) - } - - // 1 Gwei = 1e9 Wei - gwei := new(big.Float).Quo(val, new(big.Float).SetInt64(1e9)) - if gwei.Cmp(new(big.Float).SetFloat64(0.001)) >= 0 { - str := gwei.Text('g', 3) - return fmt.Sprintf("%s Gwei", str) - } - - // Wei - return fmt.Sprintf("%s Wei", b.Text(10)) -} diff --git a/devnet-sdk/types/balance_test.go b/devnet-sdk/types/balance_test.go deleted file mode 100644 index 09b880f60622f..0000000000000 --- a/devnet-sdk/types/balance_test.go +++ /dev/null @@ -1,267 +0,0 @@ -package types - -import ( - "math/big" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNewBalance(t *testing.T) { - i := big.NewInt(100) - b := NewBalance(i) - if b.Int.Cmp(i) != 0 { - t.Errorf("NewBalance failed, got %v, want %v", b.Int, i) - } - - // Verify that modifying the input doesn't affect the Balance - i.SetInt64(200) - if b.Int.Cmp(big.NewInt(100)) != 0 { - t.Error("NewBalance did not create a copy of the input") - } -} - -func TestBalance_Add(t *testing.T) { - tests := []struct { - a, b, want int64 - }{ - {100, 200, 300}, - {0, 100, 100}, - {-100, 100, 0}, - {1000000, 2000000, 3000000}, - } - - for _, tt := range tests { - a := NewBalance(big.NewInt(tt.a)) - b := NewBalance(big.NewInt(tt.b)) - got := a.Add(b) - want := NewBalance(big.NewInt(tt.want)) - if !got.Equal(want) { - t.Errorf("Add(%v, %v) = %v, want %v", tt.a, tt.b, got, want) - } - // Verify original balances weren't modified - if !a.Equal(NewBalance(big.NewInt(tt.a))) { - t.Error("Add modified original balance") - } - } -} - -func TestBalance_Sub(t *testing.T) { - tests := []struct { - a, b, want int64 - }{ - {300, 200, 100}, - {100, 100, 0}, - {0, 100, -100}, - {3000000, 2000000, 1000000}, - } - - for _, tt := range tests { - a := NewBalance(big.NewInt(tt.a)) - b := NewBalance(big.NewInt(tt.b)) - got := a.Sub(b) - want := NewBalance(big.NewInt(tt.want)) - if !got.Equal(want) { - t.Errorf("Sub(%v, %v) = %v, want %v", tt.a, tt.b, got, want) - } - } -} - -func TestBalance_Mul(t *testing.T) { - tests := []struct { - a int64 - mul float64 - want int64 - }{ - {100, 2.0, 200}, - {100, 0.5, 50}, - {100, 0.0, 0}, - {1000, 1.5, 1500}, - } - - for _, tt := range tests { - a := NewBalance(big.NewInt(tt.a)) - got := a.Mul(tt.mul) - want := NewBalance(big.NewInt(tt.want)) - if !got.Equal(want) { - t.Errorf("Mul(%v, %v) = %v, want %v", tt.a, tt.mul, got, want) - } - } -} - -func TestBalanceComparisons(t *testing.T) { - tests := []struct { - name string - balance1 Balance - balance2 Balance - greater bool - less bool - equal bool - }{ - { - name: "both nil", - balance1: Balance{}, - balance2: Balance{}, - greater: false, - less: false, - equal: true, - }, - { - name: "first nil", - balance1: Balance{}, - balance2: NewBalance(big.NewInt(100)), - greater: false, - less: true, - equal: false, - }, - { - name: "second nil", - balance1: NewBalance(big.NewInt(100)), - balance2: Balance{}, - greater: true, - less: false, - equal: false, - }, - { - name: "first greater", - balance1: NewBalance(big.NewInt(200)), - balance2: NewBalance(big.NewInt(100)), - greater: true, - less: false, - equal: false, - }, - { - name: "second greater", - balance1: NewBalance(big.NewInt(100)), - balance2: NewBalance(big.NewInt(200)), - greater: false, - less: true, - equal: false, - }, - { - name: "equal values", - balance1: NewBalance(big.NewInt(100)), - balance2: NewBalance(big.NewInt(100)), - greater: false, - less: false, - equal: true, - }, - { - name: "zero values", - balance1: NewBalance(new(big.Int)), - balance2: NewBalance(new(big.Int)), - greater: false, - less: false, - equal: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.greater, tt.balance1.GreaterThan(tt.balance2), "GreaterThan check failed") - assert.Equal(t, tt.less, tt.balance1.LessThan(tt.balance2), "LessThan check failed") - assert.Equal(t, tt.equal, tt.balance1.Equal(tt.balance2), "Equal check failed") - }) - } -} - -func TestBalanceArithmetic(t *testing.T) { - tests := []struct { - name string - balance1 Balance - balance2 Balance - add *big.Int - sub *big.Int - mul float64 - mulRes *big.Int - }{ - { - name: "basic arithmetic", - balance1: NewBalance(big.NewInt(100)), - balance2: NewBalance(big.NewInt(50)), - add: big.NewInt(150), - sub: big.NewInt(50), - mul: 2.5, - mulRes: big.NewInt(250), - }, - { - name: "zero values", - balance1: NewBalance(new(big.Int)), - balance2: NewBalance(new(big.Int)), - add: new(big.Int), - sub: new(big.Int), - mul: 1.0, - mulRes: new(big.Int), - }, - { - name: "large numbers", - balance1: NewBalance(new(big.Int).Mul(big.NewInt(1e18), big.NewInt(100))), // 100 ETH - balance2: NewBalance(new(big.Int).Mul(big.NewInt(1e18), big.NewInt(50))), // 50 ETH - add: new(big.Int).Mul(big.NewInt(1e18), big.NewInt(150)), // 150 ETH - sub: new(big.Int).Mul(big.NewInt(1e18), big.NewInt(50)), // 50 ETH - mul: 0.5, - mulRes: new(big.Int).Mul(big.NewInt(1e18), big.NewInt(50)), // 50 ETH - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Test Add - sum := tt.balance1.Add(tt.balance2) - assert.Equal(t, 0, sum.Int.Cmp(tt.add), "Add result mismatch") - - // Test Sub - diff := tt.balance1.Sub(tt.balance2) - assert.Equal(t, 0, diff.Int.Cmp(tt.sub), "Sub result mismatch") - - // Test Mul - product := tt.balance1.Mul(tt.mul) - assert.Equal(t, 0, product.Int.Cmp(tt.mulRes), "Mul result mismatch") - }) - } -} - -func TestBalanceLogValue(t *testing.T) { - tests := []struct { - name string - balance Balance - expected string - }{ - { - name: "nil balance", - balance: Balance{}, - expected: "0 ETH", - }, - { - name: "zero balance", - balance: NewBalance(new(big.Int)), - expected: "0 Wei", - }, - { - name: "small wei amount", - balance: NewBalance(big.NewInt(100)), - expected: "100 Wei", - }, - { - name: "gwei amount", - balance: NewBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(1e9))), - expected: "1 Gwei", - }, - { - name: "eth amount", - balance: NewBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(1e18))), - expected: "1 ETH", - }, - { - name: "large eth amount", - balance: NewBalance(new(big.Int).Mul(big.NewInt(1000), big.NewInt(1e18))), - expected: "1000 ETH", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.expected, tt.balance.String()) - }) - } -} diff --git a/devnet-sdk/types/types.go b/devnet-sdk/types/types.go deleted file mode 100644 index e251710ae7193..0000000000000 --- a/devnet-sdk/types/types.go +++ /dev/null @@ -1,30 +0,0 @@ -package types - -import ( - "context" - "crypto/ecdsa" - "math/big" - - "github.com/ethereum/go-ethereum/common" -) - -type Address = common.Address - -type ChainID = *big.Int - -type ReadInvocation[T any] interface { - Call(ctx context.Context) (T, error) -} - -type WriteInvocation[T any] interface { - ReadInvocation[T] - Send(ctx context.Context) InvocationResult -} - -type InvocationResult interface { - Error() error - Wait() error - Info() any -} - -type Key = *ecdsa.PrivateKey diff --git a/go.mod b/go.mod index 4e4aa1378021a..77c899b2144a5 100644 --- a/go.mod +++ b/go.mod @@ -19,14 +19,10 @@ require ( github.com/crate-crypto/go-kzg-4844 v1.1.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 - github.com/docker/docker v27.5.1+incompatible - github.com/docker/go-connections v0.5.0 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.4-0.20251001155152-4eb15ccedf7e github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20260115192958-fb86a23cd30e github.com/ethereum/go-ethereum v1.16.3 - github.com/fatih/color v1.18.0 github.com/fsnotify/fsnotify v1.9.0 - github.com/go-task/slim-sprig/v3 v3.0.0 github.com/golang/snappy v1.0.0 github.com/google/go-cmp v0.7.0 github.com/google/go-github/v55 v55.0.0 @@ -38,7 +34,6 @@ require ( github.com/hashicorp/raft v1.7.3 github.com/hashicorp/raft-boltdb/v2 v2.3.1 github.com/holiman/uint256 v1.3.2 - github.com/honeycombio/otel-config-go v1.17.0 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-leveldb v0.5.0 github.com/klauspost/compress v1.18.0 @@ -61,7 +56,6 @@ require ( github.com/prometheus/client_model v0.6.2 github.com/protolambda/ctxlock v0.1.0 github.com/schollz/progressbar/v3 v3.18.0 - github.com/spf13/afero v1.12.0 github.com/stretchr/testify v1.10.0 github.com/urfave/cli/v2 v2.27.6 go.etcd.io/bbolt v1.3.5 @@ -79,6 +73,11 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) +require ( + github.com/fatih/color v1.18.0 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect +) + require ( codeberg.org/go-fonts/liberation v0.5.0 // indirect codeberg.org/go-latex/latex v0.1.0 // indirect @@ -101,7 +100,6 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.5 // indirect github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect github.com/campoy/embedmd v1.0.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/cockroachdb/errors v1.11.3 // indirect @@ -110,7 +108,6 @@ require ( github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/containerd/cgroups v1.1.0 // indirect - github.com/containerd/log v0.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect @@ -121,7 +118,6 @@ require ( github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect github.com/deepmap/oapi-codegen v1.8.2 // indirect github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de // indirect - github.com/distribution/reference v0.6.0 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 // indirect @@ -133,7 +129,6 @@ require ( github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect github.com/ethereum/go-verkle v0.2.2 // indirect github.com/felixge/fgprof v0.9.5 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect github.com/flynn/noise v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect @@ -157,7 +152,6 @@ require ( github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20241009165004-a3522334989c // indirect github.com/graph-gophers/graphql-go v1.3.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-bexpr v0.1.11 // indirect github.com/hashicorp/go-hclog v1.6.2 // indirect @@ -196,7 +190,6 @@ require ( github.com/libp2p/go-netroute v0.2.1 // indirect github.com/libp2p/go-reuseport v0.4.0 // indirect github.com/libp2p/go-yamux/v4 v4.0.1 // indirect - github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect @@ -208,9 +201,6 @@ require ( github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/pointerstructure v1.2.1 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/term v0.5.2 // indirect - github.com/morikuni/aec v1.0.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect @@ -224,8 +214,6 @@ require ( github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 // indirect github.com/nwaples/rardecode v1.1.3 // indirect github.com/onsi/ginkgo/v2 v2.20.0 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect @@ -250,7 +238,6 @@ require ( github.com/pion/turn/v2 v2.1.6 // indirect github.com/pion/webrtc/v3 v3.3.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/quic-go/qpack v0.4.0 // indirect @@ -262,10 +249,7 @@ require ( github.com/rs/cors v1.11.0 // indirect github.com/rs/xid v1.6.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sethvargo/go-envconfig v1.1.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect - github.com/shirou/gopsutil/v4 v4.24.6 // indirect - github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -279,20 +263,8 @@ require ( github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/host v0.53.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect - go.opentelemetry.io/contrib/instrumentation/runtime v0.53.0 // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.28.0 // indirect - go.opentelemetry.io/contrib/propagators/ot v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/otel/sdk v1.34.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect - go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/dig v1.18.0 // indirect go.uber.org/fx v1.22.2 // indirect go.uber.org/mock v0.4.0 // indirect @@ -308,7 +280,6 @@ require ( google.golang.org/protobuf v1.36.6 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gotest.tools/v3 v3.5.2 // indirect lukechampine.com/blake3 v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index ee74c32b0e798..3a380fe3fd400 100644 --- a/go.sum +++ b/go.sum @@ -23,8 +23,6 @@ git.sr.ht/~sbinet/gg v0.6.0 h1:RIzgkizAk+9r7uPzf/VfbJHBMKUr0F5hRFxTUGMnt38= git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= @@ -112,8 +110,6 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= @@ -165,8 +161,6 @@ github.com/consensys/gnark-crypto v0.18.0/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= @@ -210,15 +204,9 @@ github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdo github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8= -github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -256,8 +244,6 @@ github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/ github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -363,7 +349,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= @@ -401,8 +386,6 @@ github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY4 github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -444,8 +427,6 @@ github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= -github.com/honeycombio/otel-config-go v1.17.0 h1:3/zig0L3IGnfgiCrEfAwBsM0rF57+TKTyJ/a8yqW2eM= -github.com/honeycombio/otel-config-go v1.17.0/go.mod h1:g2mMdfih4sYKfXBtz2mNGvo3HiQYqX4Up4pdA8JOF2s= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= @@ -568,8 +549,6 @@ github.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCy github.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= github.com/lmittmann/w3 v0.19.5 h1:WwVRyIwhRLfIahmpB1EglsB3o1XWsgydgrxIUp5upFQ= github.com/lmittmann/w3 v0.19.5/go.mod h1:pN97sGGYGvsbqOYj/ms3Pd+7k/aiK/9OpNcxMmmzSOI= -github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= -github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -622,16 +601,10 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= -github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= @@ -693,10 +666,6 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -769,8 +738,6 @@ github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDj github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= @@ -833,16 +800,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA= github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sethvargo/go-envconfig v1.1.0 h1:cWZiJxeTm7AlCvzGXrEXaSTCNgip5oJepekh/BOQuog= -github.com/sethvargo/go-envconfig v1.1.0/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/gopsutil/v4 v4.24.6 h1:9qqCSYF2pgOU+t+NgJtp7Co5+5mHF/HyKBUckySQL64= -github.com/shirou/gopsutil/v4 v4.24.6/go.mod h1:aoebb2vxetJ/yIDZISmduFvVNPHqXQ9SEJwRXxkf0RA= -github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= -github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= -github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= @@ -877,8 +836,6 @@ github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -939,30 +896,8 @@ go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/aws/lambda v0.53.0 h1:KG6fOUk3EwSH1dEpsAbsLKFbn3cFwN9xDu8plGu55zI= -go.opentelemetry.io/contrib/detectors/aws/lambda v0.53.0/go.mod h1:bSd579exEkh/P5msRcom8YzVB6NsUxYKyV+D/FYOY7Y= -go.opentelemetry.io/contrib/instrumentation/host v0.53.0 h1:X4r+5n6bSqaQUbPlSO5baoM7tBvipkT0mJFyuPFnPAU= -go.opentelemetry.io/contrib/instrumentation/host v0.53.0/go.mod h1:NTaDj8VCnJxWleEcRQRQaN36+aCZjO9foNIdJunEjUQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= -go.opentelemetry.io/contrib/instrumentation/runtime v0.53.0 h1:nOlJEAJyrcy8hexK65M+dsCHIx7CVVbybcFDNkcTcAc= -go.opentelemetry.io/contrib/instrumentation/runtime v0.53.0/go.mod h1:u79lGGIlkg3Ryw425RbMjEkGYNxSnXRyR286O840+u4= -go.opentelemetry.io/contrib/propagators/b3 v1.28.0 h1:XR6CFQrQ/ttAYmTBX2loUEFGdk1h17pxYI8828dk/1Y= -go.opentelemetry.io/contrib/propagators/b3 v1.28.0/go.mod h1:DWRkzJONLquRz7OJPh2rRbZ7MugQj62rk7g6HRnEqh0= -go.opentelemetry.io/contrib/propagators/ot v1.28.0 h1:rmlG+2pc5k5M7Y7izDrxAHZUIwDERdGMTD9oMV7llMk= -go.opentelemetry.io/contrib/propagators/ot v1.28.0/go.mod h1:MNgXIn+UrMbNGpd7xyckyo2LCHIgCdmdjEE7YNZGG+w= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0/go.mod h1:TC1pyCt6G9Sjb4bQpShH+P5R53pO6ZuGnHuuln9xMeE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 h1:BEj3SPM81McUZHYjRS5pEgNgnmzGJ5tRpU5krWnV8Bs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0/go.mod h1:9cKLGBDzI/F3NoHLQGm4ZrYdIHsvGt6ej6hUowxY0J4= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= @@ -971,8 +906,6 @@ go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4Jjx go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= @@ -1120,7 +1053,6 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1268,8 +1200,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= -gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/kurtosis-devnet/.gitignore b/kurtosis-devnet/.gitignore deleted file mode 100644 index 7b6543377da0e..0000000000000 --- a/kurtosis-devnet/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*-user.json -fileserver/upload-content/* -cmd/__debug_bin* diff --git a/kurtosis-devnet/README.md b/kurtosis-devnet/README.md deleted file mode 100644 index e6bc14a760448..0000000000000 --- a/kurtosis-devnet/README.md +++ /dev/null @@ -1,203 +0,0 @@ -# Getting Started - -Welcome to the Kurtosis Devnet! This tool helps you quickly spin up local development networks for testing and development purposes. Whether you're working on simple test networks or complex interoperability scenarios, this devnet environment has you covered. - -Running a Kurtosis Devnet has the following prerequisites: -- Kurtosis must be installed. This is automatically handled by `mise`, same as with other dev tools in this repository -- Docker Desktop must be installed and running - -Docker Desktop may be substituted by an alternative like Orbstack if you have that installed. - -# Running A Devnet - -To see available devnets, consult the `justfile` to see what `.*-devnet` targets exist, currently -- `simple-devnet` -- `interop-devnet` -- `user-devnet` - -You can read over the referenced `yaml` files located in this directory to see the network definition which would be deployed. Mini and Simple are example network definitions, and User expects a provided network definition. - -To run the Interop Devnet, simply: -``` -just interop-devnet -``` - -If all works as expected, you should see a collection of containers appear in Docker. Some of them are Kurtosis infrastructure, while others are the actual hosts for your network. You can observe that the network is running by searching for "supervisor" and watching its logs. - -## Resolving Issues - -Here is a list of potential pitfalls when running Kurtosis and known solutions. - -### `error ensuring kurtosis engine is running` -This error indicates Docker Desktop (or your alternative) is not running. - -### `network with name kt-interop-devnet already exists` -If your kurtosis network is taken down and destroyed through docker, it is possible that the network resources are left around, preventing you from starting up a new network. To resolve, run: -``` -kurtosis engine stop -docker network rm kt-interop-devnet -``` - -You can use `docker network ls` to inspect for networks to remove if the error message specifies some other network. - -# Kurtosis-devnet support - -## devnet specification - -Due to sandboxing issues across repositories, we currently rely on a slight -superset of the native optimism-package specification YAML file, via go -templates. - -So that means in particular that the regular optimism-package input is valid -here. - -Additional custom functions: - -- localDockerImage(PROJECT): builds a docker image for PROJECT based on the - current branch content. - -- localContractArtifacts(LAYER): builds a contracts bundle based on the current - branch content (note: LAYER is currently ignored, we might need to revisit) - -Example: - -```yaml -... - op_contract_deployer_params: - image: {{ localDockerImage "op-deployer" }} - l1_artifacts_locator: {{ localContractArtifacts "l1" }} - l2_artifacts_locator: {{ localContractArtifacts "l2" }} -... -``` - -The list of supported PROJECT values can be found in `justfile` as a -PROJECT-image target. Adding a target there will immediately available to the -template engine. - -## devnet deployment tool - -Located in cmd/main.go, this tool handle the creation of an enclave matching the -provided specification. - -The expected entry point for interacting with it is the corresponding -`just devnet SPEC` target. - -This takes an optional 2nd argument, that can be used to provide values for the -template interpretation. - -Note that a SPEC of the form `FOO.yaml` will yield a kurtosis enclave named -`FOO-devnet` - -Convenience targets can be added to `justfile` for specific specifications, for -example: - -```just -interop-devnet: (devnet "interop.yaml") -``` - -## devnet output - -One important aspect of the devnet workflow is that the output should be -*consumable*. Going forward we want to integrate them into larger workflows -(serving as targets for tests for example, or any other form of automation). - -To address this, the deployment tool outputs a document with (hopefully!) useful -information. Here's a short extract: - -```json -{ - "l1": { - "name": "Ethereum", - "nodes": [ - { - "cl": "http://localhost:53689", - "el": "http://localhost:53620" - } - ] - }, - "l2": [ - { - "name": "op-kurtosis-1", - "id": "2151908", - "services": { - "batcher": "http://localhost:57259" - }, - "nodes": [ - { - "cl": "http://localhost:57029", - "el": "http://localhost:56781" - } - ], - "addresses": { - "addressManager": "0x1b89c03f2d8041b2ba16b5128e613d9279195d1a", - ... - } - }, - ... - ], - "wallets": { - "baseFeeVaultRecipient": { - "address": "0xF435e3ba80545679CfC24E5766d7B02F0CCB5938", - "private_key": "0xc661dd5d4b091676d1a5f2b5110f9a13cb8682140587bd756e357286a98d2c26" - }, - ... - } -} -``` - -## further interactions - -Beyond deployment, we can interact with enclaves normally. - -In particular, cleaning up a devnet can be achieved using -`kurtosis rm FOO-devnet` and the likes. - -## Troubleshooting - -### Autofix mode - -Autofix mode helps recover from failed devnet deployments by automatically -cleaning up the environment. It has two modes: - -1. **Normal Mode** (`AUTOFIX=true`) - - Sets up the correct shell and updates dependencies - - Cleans up dangling networks and stopped devnets - - Preserves other running enclaves - - Good for fixing minor deployment issues - -2. **Nuke Mode** (`AUTOFIX=nuke`) - - Sets up the correct shell and updates dependencies - - Completely resets the Kurtosis environment - - Removes all networks and containers - - Use when you need a fresh start - -Usage: -```bash -# For normal cleanup -AUTOFIX=true just interop-devnet - -# For complete reset -AUTOFIX=nuke just interop-devnet -``` - -Note: Nuke mode will stop all running enclaves, so use it carefully. - -### Older kurtosis versions - -In some cases, a newer kurtosis client might not be able to handle an -older kurtosis engine. This typically happens if the kurtosis -command-line managed by mise gets updated while some enclaves are -already running. - -To help recover, you can either run with `AUTOFIX=nuke` or kill the -old engine with: - -```shell -docker rm -f $(docker ps -aqf "name=kurtosis-*") -``` - -Potentially you'll also need to cleanup dangling docker networks: - -```shell -docker network rm -f $(docker network ls -qf "name=kt-*") -``` diff --git a/kurtosis-devnet/book/.gitignore b/kurtosis-devnet/book/.gitignore deleted file mode 100644 index 7585238efedfc..0000000000000 --- a/kurtosis-devnet/book/.gitignore +++ /dev/null @@ -1 +0,0 @@ -book diff --git a/kurtosis-devnet/book/book.toml b/kurtosis-devnet/book/book.toml deleted file mode 100644 index ac835adb54a1c..0000000000000 --- a/kurtosis-devnet/book/book.toml +++ /dev/null @@ -1,13 +0,0 @@ -[book] -authors = ["Optimism Contributors"] -language = "en" -multilingual = false -src = "src" -title = "Kurtosis Devnet Book" - -[output.html] -site-url = "/kurtosis-devnet/" -git-repository-url = "https://github.com/ethereum-optimism/optimism/tree/develop/kurtosis-devnet/book" -edit-url-template = "https://github.com/ethereum-optimism/optimism/tree/develop/kurtosis-devnet/book/{path}" -additional-css = ["custom.css", "theme/css/footer.css"] -additional-js = ["theme/js/footer.js"] diff --git a/kurtosis-devnet/book/custom.css b/kurtosis-devnet/book/custom.css deleted file mode 100644 index 7c94143752af4..0000000000000 --- a/kurtosis-devnet/book/custom.css +++ /dev/null @@ -1,5 +0,0 @@ -.content main { - max-width: 85%; - margin-left: auto; - margin-right: auto; -} diff --git a/kurtosis-devnet/book/src/README.md b/kurtosis-devnet/book/src/README.md deleted file mode 100644 index dfc864106293a..0000000000000 --- a/kurtosis-devnet/book/src/README.md +++ /dev/null @@ -1,26 +0,0 @@ -> ⚠️ **UNDER HEAVY DEVELOPMENT** ⚠️ -> -> This documentation is actively being developed and may change frequently. - -# Introduction - -Kurtosis Devnet is a development and testing environment for Optimism devnets, providing a local development setup for testing and validating L2 functionality. -This environment is built around Kurtosis, adding convenience features for dev-oriented features. - -## Getting Started - -To use Kurtosis Devnet, you'll need: -1. A Docker daemon (Docker Desktop or any drop-in replacement) -2. Kurtosis installed on your system - -Detailed setup instructions and usage examples can be found in the following chapters of this documentation. - -## Use Cases - -The Kurtosis DevNet is particularly useful for: -- Protocol developers working on Optimism -- Smart contract developers testing L2 functionality -- Engineers validating cross-chain interactions - -This documentation will guide you through setting up, using, and extending Kurtosis Devnet for your development needs. - diff --git a/kurtosis-devnet/book/src/SUMMARY.md b/kurtosis-devnet/book/src/SUMMARY.md deleted file mode 100644 index 8d9c720ff5607..0000000000000 --- a/kurtosis-devnet/book/src/SUMMARY.md +++ /dev/null @@ -1,12 +0,0 @@ -# Summary - -[Introduction](README.md) - -# Getting started - -- [Basic deployment](./basic_deployment.md) - -# Architecture - -- [Local Artifacts](./local_artifacts.md) -- [Standardized Output](./std_output.md) \ No newline at end of file diff --git a/kurtosis-devnet/book/src/basic_deployment.md b/kurtosis-devnet/book/src/basic_deployment.md deleted file mode 100644 index 6a3ad2e064f63..0000000000000 --- a/kurtosis-devnet/book/src/basic_deployment.md +++ /dev/null @@ -1,233 +0,0 @@ -# Basic Deployment - -The Kurtosis devnet provides several pre-configured devnet templates and convenient commands to deploy and interact with them. - -## Built-in Devnets - -The following devnet templates are available out of the box: - -1. **Simple Devnet** (`simple.yaml`) - - Basic single-chain setup - - Ideal for local development and testing - - Deploy with: `just simple-devnet` - -2. **Interop Devnet** (`interop.yaml`) - - Designed for interop testing - - Includes test suite for cross-chain interactions - - Deploy with: `just interop-devnet` - - Run tests with: `just interop-devnet-test` - -3. **Pectra Devnet** (`pectra.yaml`) - - Specialized configuration for Pectra testing - - Deploy with: `just pectra-devnet` - -## User-Defined Devnets (Experimental) - -> **Note**: User-defined devnets are an experimental feature and not actively supported at this time. Use at your own risk. - -The user devnet template (`user.yaml`) allows for customizable devnet configurations through a JSON input file. This feature is designed to simplify devnet creation for future devnet-as-a-service scenarios. - -### Deployment -```bash -just user-devnet -``` - -### Example Configuration -Here's an example of a user devnet configuration file: - -```json -{ - "interop": true, - "l2s": { - "2151908": { - "nodes": ["op-geth", "op-geth"] - }, - "2151909": { - "nodes": ["op-reth"] - } - }, - "overrides": { - "flags": { - "log_level": "--log.level=debug" - } - } -} -``` - -This configuration: -- Enables interop testing features -- Defines two L2 chains: - - Chain `2151908` with two `op-geth` nodes - - Chain `2151909` with one `op-reth` node -- Sets custom logging level for all nodes - -## Deployment Commands - -Arbitrary devnets can be deployed using the general `devnet` command with the following syntax: -```bash -just devnet [data-file] [name] -``` - -Where: -- `template-file`: The YAML template to use (e.g., `simple.yaml`) -- `data-file`: Optional JSON file with configuration data -- `name`: Optional custom name for the devnet (defaults to template name) - -For example: -```bash -# Deploy simple devnet with default name -just devnet simple.yaml - -# Deploy user devnet with custom data and name -just devnet user.yaml my-config.json my-custom-devnet -``` - -This can be convenient when experimenting with devnet definitions - -## Entering a Devnet Shell - -The devnet provides a powerful feature to "enter" a devnet environment, which sets up the necessary environment variables for interacting with the chains. - -### Basic Usage -```bash -just enter-devnet [chain-name] -``` - -Where: -- `devnet-name`: The name of your deployed devnet -- `chain-name`: Optional chain to connect to (defaults to "Ethereum") - -Example: -```bash -# Enter the Ethereum chain environment in the simple devnet -just enter-devnet simple-devnet - -# Enter a specific chain environment -just enter-devnet my-devnet l2-chain - -# Use exec to replace the current shell process (recommended) -exec just enter-devnet my-devnet l2-chain -``` - -Note: The enter feature creates a new shell process. To avoid accumulating shell processes, you can use the `exec` command, which replaces the current shell with the new one. This is especially useful in scripts or when you want to maintain a clean process tree. - -### Features of the Devnet Shell - -When you enter a devnet shell, you get: -1. All necessary environment variables set for the chosen chain -2. Integration with tools like `cast` for blockchain interaction -3. Chain-specific configuration and endpoints -4. A new shell session with the devnet context - -The shell inherits your current environment and adds: -- Chain-specific RPC endpoints -- Network identifiers -- Authentication credentials (if any) -- Tool configurations - -To exit the devnet shell, simply type `exit` or press `Ctrl+D`. - -### Environment Variables - -The devnet shell automatically sets up environment variables needed for development and testing: -- `ETH_RPC_URL`: The RPC endpoint for the selected chain -- `ETH_RPC_JWT_SECRET`: JWT secret for authenticated RPC connections (when cast integration is enabled) -- `DEVNET_ENV_URL`: The URL or absolute path to the devnet environment file -- `DEVNET_CHAIN_NAME`: The name of the currently selected chain - -These variables are automatically picked up by tools like `cast`, making it easy to interact with the chain directly from the shell. - -## AUTOFIX Feature - -The devnet includes an AUTOFIX feature that helps recover from failed devnet deployments by automatically cleaning up the environment. It has two modes: - -1. **Normal Mode** (`AUTOFIX=true`) - - Cleans up stopped or empty enclaves - - Removes associated Docker resources (containers, volumes, networks) - - Preserves running enclaves - - Good for fixing minor deployment issues - -2. **Nuke Mode** (`AUTOFIX=nuke`) - - Completely resets the Kurtosis environment - - Removes all enclaves and associated Docker resources - - Use when you need a fresh start - -### How AUTOFIX Works - -AUTOFIX operates by: -1. Checking the status of the enclave (running, stopped, or empty) -2. For stopped or empty enclaves in normal mode: - - Removes the enclave - - Cleans up potential kurtosis Docker resources -3. For nuke mode: - - Removes all enclaves - - Cleans up all potential kurtosis Docker resources - -### Usage - -```bash -# For normal cleanup -AUTOFIX=true just devnet simple.yaml - -# For complete reset -AUTOFIX=nuke just devnet simple.yaml -``` - -Note: Nuke mode will stop all running enclaves, so use it carefully. - -### Troubleshooting - -If you encounter issues with older Kurtosis versions, you can use AUTOFIX to recover: - -```bash -# For normal cleanup -AUTOFIX=true just devnet simple.yaml - -# For complete reset -AUTOFIX=nuke just devnet simple.yaml -``` - -Alternatively, you can manually clean up Docker resources: - -```bash -# Remove old Kurtosis containers -docker rm -f $(docker ps -aqf "name=kurtosis-*") - -# Clean up dangling networks -docker network rm -f $(docker network ls -qf "name=kt-*") -``` - -## Frequently Asked Questions (FAQ) - -### Docker Rate Limiting Issues - -#### Q: I'm getting a 443 error when pulling from ghcr.io. What can I do? - -A: This is typically caused by Docker Hub rate limiting. Here are several solutions: - -1. **Authenticate with GitHub Container Registry**: - ```bash - docker login ghcr.io - ``` - This will give you higher rate limits. - -2. **Adjust Docker Engine Configuration**: - Add these settings to your Docker daemon configuration (`/etc/docker/daemon.json`): - ```json - { - "max-concurrent-downloads": 1, - "max-concurrent-uploads": 1, - "max-download-attempts": 100, - "registry-mirrors": [] - } - ``` - -3. **Restart Docker Engine**: - ```bash - # For systemd-based systems - sudo systemctl restart docker - - # For macOS - osascript -e 'quit app "Docker"' - open -a Docker - ``` diff --git a/kurtosis-devnet/book/src/local_artifacts.md b/kurtosis-devnet/book/src/local_artifacts.md deleted file mode 100644 index 49342518336a8..0000000000000 --- a/kurtosis-devnet/book/src/local_artifacts.md +++ /dev/null @@ -1,114 +0,0 @@ -# Local Artifacts Integration - -The Kurtosis devnet provides powerful templating capabilities that allow you to seamlessly integrate locally built artifacts (Docker images, smart contracts, and prestates) into your devnet configuration. This integration is managed through a combination of Go-based builders and YAML templates. - -## Component Eligibility - -Not all components can be built locally. Only components that are part of the Optimism monorepo can be built using the local artifact system. Here's a breakdown: - -### Buildable Components -Components that can be built locally include: -- `op-node` -- `op-batcher` -- `op-proposer` -- `op-challenger` -- `op-deployer` - -### External Components -Some components are dependencies living outside the monorepo and cannot be built locally: -- `op-geth` -- `op-reth` - -For example, in your configuration: -```yaml -# This will use an external image - cannot be built locally -el_type: op-geth -el_image: "" # Will use the default op-geth image - -# This can be built locally -cl_type: op-node -cl_image: {{ localDockerImage "op-node" }} # Will build from local source -``` - -## Template Functions - -In the `simple.yaml` configuration, you'll notice several custom template functions that enable local artifact integration: - -```yaml -# Example usage in simple.yaml -image: {{ localDockerImage "op-node" }} -l1_artifacts_locator: {{ localContractArtifacts "l1" }} -faultGameAbsolutePrestate: {{ localPrestate.Hashes.prestate_mt64 }} -``` - -These template functions map to specific builders in the Go codebase that handle artifact construction. - -## Builder Components - -### 1. Docker Image Builder - -The Docker image builder manages the building and tagging of local Docker images: - -```go -// Usage in YAML: -image: {{ localDockerImage "op-node" }} -``` - -This builder: -- Executes build commands using the `just` task runner -- Caches built images to prevent redundant builds (in particular when we have multiple L2s and/or participants to any L2) - -### 2. Contract Builder - -The contract builder handles the compilation and bundling of smart contracts: - -```yaml -# Usage in YAML: -l1_artifacts_locator: {{ localContractArtifacts "l1" }} -l2_artifacts_locator: {{ localContractArtifacts "l2" }} -``` - -This builder: -- Manages contract compilation through `just` commands -- Caches built contract bundles - -### 3. Prestate Builder - -The prestate builder manages the generation of fault proof prestates: - -```yaml -# Usage in YAML: -faultGameAbsolutePrestate: {{ localPrestate.Hashes.prestate_mt64 }} -``` - -This builder: -- Generates prestate data for fault proofs -- Caches built prestates - -## Using Local Artifacts - -To use local artifacts in your devnet: - -1. Ensure your local environment has the necessary build dependencies -2. Reference local artifacts in your YAML configuration using the appropriate template functions -3. The builders will automatically handle building and caching of artifacts - -Example configuration using all types of local artifacts: - -```yaml -optimism_package: - chains: - - participants: - - el_type: op-geth - el_image: "" # Uses default external op-geth image - cl_type: op-node - cl_image: {{ localDockerImage "op-node" }} - op_contract_deployer_params: - image: {{ localDockerImage "op-deployer" }} - l1_artifacts_locator: {{ localContractArtifacts "l1" }} - l2_artifacts_locator: {{ localContractArtifacts "l2" }} - global_deploy_overrides: - faultGameAbsolutePrestate: {{ localPrestate.Hashes.prestate_mt64 }} -``` - -This integration system ensures that your devnet can seamlessly use locally built components while maintaining reproducibility and ease of configuration. diff --git a/kurtosis-devnet/book/src/std_output.md b/kurtosis-devnet/book/src/std_output.md deleted file mode 100644 index d62c5acf34abe..0000000000000 --- a/kurtosis-devnet/book/src/std_output.md +++ /dev/null @@ -1,134 +0,0 @@ -# Standard Output Format - -Kurtosis-devnet is tightly integrated with the [Optimism Devnet SDK](/devnet-sdk/). This integration is achieved through a standardized devnet descriptor format that enables powerful testing and automation capabilities. - -## Accessing the Devnet Descriptor - -The devnet descriptor is available in two ways: - -1. **Deployment Output** - - When you run any of the deployment commands (`just devnet ...`), the descriptor is printed to stdout - - The output is a JSON file that fully describes your devnet configuration - - You can capture this output for later use or automation - -2. **Kurtosis Enclave Artifact** - - The descriptor is also stored as a file artifact named "devnet" in the Kurtosis enclave - - This allows other tools and services to discover and interact with your devnet - - The descriptor can be accessed through devnet-sdk using the Kurtosis URL format: `kt:///files/devnet` - -Here's a simplified example of a devnet descriptor: - -```json -{ - "l1": { - "name": "Ethereum", - "nodes": [ - { - "services": { - "cl": { - "name": "cl-1-lighthouse-geth", - "endpoints": { - "http": { - "host": "127.0.0.1", - "port": 8545 - } - } - }, - "el": { - "name": "el-1-geth-lighthouse", - "endpoints": { - "rpc": { - "host": "127.0.0.1", - "port": 8551 - } - } - } - } - } - ], - "addresses": { - "l1CrossDomainMessenger": "0x...", - "l1StandardBridge": "0x...", - "optimismPortal": "0x..." - // ... other contract addresses - }, - "wallets": { - "user-key-0": { - "address": "0x...", - "private_key": "0x..." - } - // ... other wallets - }, - "jwt": "0x..." - }, - "l2": [ - { - "name": "op-kurtosis", - "id": "2151908", - "services": { - "batcher": { - "name": "op-batcher-op-kurtosis", - "endpoints": { - "http": { - "host": "127.0.0.1", - "port": 8547 - } - } - }, - "proposer": { - "name": "op-proposer-op-kurtosis", - "endpoints": { - "http": { - "host": "127.0.0.1", - "port": 8548 - } - } - } - }, - "nodes": [ - { - "services": { - "cl": { - "name": "op-node", - "endpoints": { - "http": { - "host": "127.0.0.1", - "port": 8546 - } - } - }, - "el": { - "name": "op-geth", - "endpoints": { - "rpc": { - "host": "127.0.0.1", - "port": 8549 - } - } - } - } - } - ], - "jwt": "0x..." - } - ] -} -``` - -This standardized output enables seamless integration with the devnet-sdk and other tools in the ecosystem. - -## Devnet SDK Integration - -By leveraging the devnet-sdk integration, your devnets automatically gain access to: - -1. **Test Framework Integration** - - Use your devnet as a System Under Test (SUT) with tests written in the devnet-sdk framework - - Seamless integration with existing test suites - - Standardized approach to devnet interaction in tests - -2. **Test Runner Support** - - Native support for op-nat as a test runner - - Consistent test execution across different devnet configurations - - Automated test setup and teardown - -These capabilities make kurtosis-devnet an ideal platform for both development and testing environments. diff --git a/kurtosis-devnet/book/theme/css/footer.css b/kurtosis-devnet/book/theme/css/footer.css deleted file mode 100644 index cb7be80ab2145..0000000000000 --- a/kurtosis-devnet/book/theme/css/footer.css +++ /dev/null @@ -1,71 +0,0 @@ -.mdbook-footer { - width: 100%; - padding: 4rem 2.5rem; /* Increased padding */ - background-color: var(--bg); - border-top: 1px solid var(--sidebar-bg); - margin-top: 5rem; /* Increased margin */ -} - -.mdbook-footer .footer-container { - max-width: 1200px; - margin: 0 auto; - display: flex; - flex-direction: column; - gap: 2.5rem; /* Increased gap */ - align-items: center; -} - -.mdbook-footer .policy-links { - display: flex; - gap: 4rem; /* Increased gap between links */ - flex-wrap: wrap; - justify-content: center; -} - -.mdbook-footer .policy-links a { - color: var(--fg); - text-decoration: none; - transition: opacity 0.2s; - font-size: 1.35rem; /* Increased font size */ - opacity: 0.85; - font-weight: 400; - line-height: 1.6; /* Increased line height */ -} - -.mdbook-footer .policy-links a:hover { - opacity: 1; - text-decoration: underline; -} - -.mdbook-footer .copyright { - color: var(--fg); - font-size: 1.35rem; /* Increased font size */ - opacity: 0.85; - text-align: center; - font-weight: 400; - line-height: 1.6; /* Increased line height */ -} - -.mdbook-footer .copyright a { - color: var(--fg); - text-decoration: none; -} - -.mdbook-footer .copyright a:hover { - text-decoration: underline; -} - -@media (max-width: 640px) { - .mdbook-footer .policy-links { - gap: 2.5rem; /* Increased gap for mobile */ - } - - .mdbook-footer { - padding: 3rem 2rem; /* Increased padding for mobile */ - } - - .mdbook-footer .policy-links a, - .mdbook-footer .copyright { - font-size: 1.25rem; /* Increased font size for mobile */ - } -} \ No newline at end of file diff --git a/kurtosis-devnet/book/theme/js/footer.js b/kurtosis-devnet/book/theme/js/footer.js deleted file mode 100644 index 014f44f2d6c54..0000000000000 --- a/kurtosis-devnet/book/theme/js/footer.js +++ /dev/null @@ -1,41 +0,0 @@ -// Create footer element -function createFooter() { - const footer = document.createElement('footer'); - footer.className = 'mdbook-footer'; - - const container = document.createElement('div'); - container.className = 'footer-container'; - - // Add legal links - const policyLinks = document.createElement('div'); - policyLinks.className = 'policy-links'; - - const links = [ - { href: 'https://optimism.io/community-agreement', text: 'Community Agreement' }, - { href: 'https://optimism.io/terms', text: 'Terms of Service' }, - { href: 'https://optimism.io/data-privacy-policy', text: 'Privacy Policy' } - ]; - - links.forEach(link => { - const a = document.createElement('a'); - a.href = link.href; - a.textContent = link.text; - policyLinks.appendChild(a); - }); - - // Add copyright notice - const copyright = document.createElement('div'); - copyright.className = 'copyright'; - copyright.innerHTML = `© ${new Date().getFullYear()} Optimism Foundation. All rights reserved.`; - - // Assemble footer - container.appendChild(policyLinks); - container.appendChild(copyright); - footer.appendChild(container); - - // Add footer to page - document.body.appendChild(footer); -} - -// Run after DOM is loaded -document.addEventListener('DOMContentLoaded', createFooter); \ No newline at end of file diff --git a/kurtosis-devnet/cmd/main.go b/kurtosis-devnet/cmd/main.go deleted file mode 100644 index e9a651cb94362..0000000000000 --- a/kurtosis-devnet/cmd/main.go +++ /dev/null @@ -1,246 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "log" - "os" - "path/filepath" - - "github.com/BurntSushi/toml" - "github.com/ethereum-optimism/optimism/devnet-sdk/telemetry" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/deploy" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/inspect" - autofixTypes "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/types" - "github.com/honeycombio/otel-config-go/otelconfig" - "github.com/urfave/cli/v2" -) - -type config struct { - templateFile string - dataFile string - kurtosisPackage string - enclave string - environment string - conductorConfig string - dryRun bool - baseDir string - kurtosisBinary string - autofix string -} - -func newConfig(c *cli.Context) (*config, error) { - cfg := &config{ - templateFile: c.String("template"), - dataFile: c.String("data"), - kurtosisPackage: c.String("kurtosis-package"), - enclave: c.String("enclave"), - environment: c.String("environment"), - conductorConfig: c.String("conductor-config"), - dryRun: c.Bool("dry-run"), - kurtosisBinary: c.String("kurtosis-binary"), - autofix: c.String("autofix"), - } - - // Validate required flags - if cfg.templateFile == "" { - return nil, fmt.Errorf("template file is required") - } - cfg.baseDir = filepath.Dir(cfg.templateFile) - - return cfg, nil -} - -func writeEnvironment(path string, env *kurtosis.KurtosisEnvironment) error { - out := os.Stdout - if path != "" { - var err error - out, err = os.Create(path) - if err != nil { - return fmt.Errorf("error creating environment file: %w", err) - } - defer out.Close() - } - - enc := json.NewEncoder(out) - enc.SetIndent("", " ") - if err := enc.Encode(env); err != nil { - return fmt.Errorf("error encoding environment: %w", err) - } - - return nil -} - -func writeConductorConfig(path string, enclaveName string) error { - if path == "" { - return nil - } - - ctx := context.Background() - conductorConfig, err := inspect.ExtractConductorConfig(ctx, enclaveName) - if err != nil { - log.Printf("Warning: Could not extract conductor config: %v", err) - return nil - } - - if conductorConfig == nil { - log.Println("No conductor services found, skipping conductor config generation") - return nil - } - - out, err := os.Create(path) - if err != nil { - return fmt.Errorf("error creating conductor config file: %w", err) - } - defer out.Close() - - encoder := toml.NewEncoder(out) - if err := encoder.Encode(conductorConfig); err != nil { - return fmt.Errorf("error encoding conductor config as TOML: %w", err) - } - - log.Printf("Conductor configuration saved to: %s", path) - return nil -} - -func printAutofixMessage() { - fmt.Println("Trouble with your devnet? Try Autofix!") - fmt.Println("Set AUTOFIX=true to automatically fix common configuration issues.") - fmt.Println("If that doesn't work, set AUTOFIX=nuke to start fresh with a clean slate.") - fmt.Println() -} - -func printWelcomeMessage() { - fmt.Println("Welcome to Kurtosis Devnet!") - printAutofixMessage() - fmt.Println("Happy hacking!") -} - -func mainAction(c *cli.Context) error { - ctx := c.Context - - ctx, shutdown, err := telemetry.SetupOpenTelemetry( - ctx, - otelconfig.WithServiceName(c.App.Name), - otelconfig.WithServiceVersion(c.App.Version), - ) - if err != nil { - return fmt.Errorf("error setting up OpenTelemetry: %w", err) - } - defer shutdown() - - // Only show welcome message if not showing help or version - if !c.Bool("help") && !c.Bool("version") && c.NArg() == 0 { - printWelcomeMessage() - } - - cfg, err := newConfig(c) - if err != nil { - return fmt.Errorf("error parsing config: %w", err) - } - - autofixMode := autofixTypes.AutofixModeDisabled - if cfg.autofix == "true" { - autofixMode = autofixTypes.AutofixModeNormal - } else if cfg.autofix == "nuke" { - autofixMode = autofixTypes.AutofixModeNuke - } else if os.Getenv("AUTOFIX") == "true" { - autofixMode = autofixTypes.AutofixModeNormal - } else if os.Getenv("AUTOFIX") == "nuke" { - autofixMode = autofixTypes.AutofixModeNuke - } - - deployer, err := deploy.NewDeployer( - deploy.WithKurtosisPackage(cfg.kurtosisPackage), - deploy.WithEnclave(cfg.enclave), - deploy.WithDryRun(cfg.dryRun), - deploy.WithKurtosisBinary(cfg.kurtosisBinary), - deploy.WithTemplateFile(cfg.templateFile), - deploy.WithDataFile(cfg.dataFile), - deploy.WithBaseDir(cfg.baseDir), - deploy.WithAutofixMode(autofixMode), - ) - if err != nil { - return fmt.Errorf("error creating deployer: %w", err) - } - - env, err := deployer.Deploy(ctx, nil) - if err != nil { - if autofixMode == autofixTypes.AutofixModeDisabled { - printAutofixMessage() - } - return fmt.Errorf("error deploying environment: %w", err) - } - - // Write environment JSON file - if err := writeEnvironment(cfg.environment, env); err != nil { - return fmt.Errorf("error writing environment file: %w", err) - } - - // Write conductor configuration TOML file - if err := writeConductorConfig(cfg.conductorConfig, cfg.enclave); err != nil { - return fmt.Errorf("error writing conductor config file: %w", err) - } - - return nil -} - -func getFlags() []cli.Flag { - return []cli.Flag{ - &cli.StringFlag{ - Name: "template", - Usage: "Path to the template file (required)", - Required: true, - }, - &cli.StringFlag{ - Name: "data", - Usage: "Path to JSON data file (optional)", - }, - &cli.StringFlag{ - Name: "kurtosis-package", - Usage: "Kurtosis package to deploy (optional)", - Value: kurtosis.DefaultPackageName, - }, - &cli.StringFlag{ - Name: "enclave", - Usage: "Enclave name (optional)", - Value: kurtosis.DefaultEnclave, - }, - &cli.StringFlag{ - Name: "environment", - Usage: "Path to JSON environment file output (optional)", - }, - &cli.StringFlag{ - Name: "conductor-config", - Usage: "Path to TOML conductor configuration file output (optional)", - }, - &cli.BoolFlag{ - Name: "dry-run", - Usage: "Dry run mode (optional)", - }, - &cli.StringFlag{ - Name: "kurtosis-binary", - Usage: "Path to kurtosis binary (optional)", - Value: "kurtosis", - }, - &cli.StringFlag{ - Name: "autofix", - Usage: "Autofix mode (optional, values: true, nuke)", - }, - } -} - -func main() { - app := &cli.App{ - Name: "kurtosis-devnet", - Usage: "Deploy and manage Optimism devnet using Kurtosis", - Flags: getFlags(), - Action: mainAction, - } - - if err := app.Run(os.Args); err != nil { - log.Fatalf("Error: %v\n", err) - } -} diff --git a/kurtosis-devnet/cmd/main_test.go b/kurtosis-devnet/cmd/main_test.go deleted file mode 100644 index 6ea2d8a0be7c3..0000000000000 --- a/kurtosis-devnet/cmd/main_test.go +++ /dev/null @@ -1,293 +0,0 @@ -package main - -import ( - "os" - "path/filepath" - "testing" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis" - autofixTypes "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/urfave/cli/v2" -) - -func TestParseFlags(t *testing.T) { - tests := []struct { - name string - args []string - wantCfg *config - wantError bool - }{ - { - name: "valid configuration", - args: []string{ - "--template", "path/to/template.yaml", - "--enclave", "test-enclave", - }, - wantCfg: &config{ - templateFile: "path/to/template.yaml", - enclave: "test-enclave", - kurtosisPackage: kurtosis.DefaultPackageName, - }, - wantError: false, - }, - { - name: "missing required template", - args: []string{"--enclave", "test-enclave"}, - wantCfg: nil, - wantError: true, - }, - { - name: "with data file", - args: []string{ - "--template", "path/to/template.yaml", - "--data", "path/to/data.json", - }, - wantCfg: &config{ - templateFile: "path/to/template.yaml", - dataFile: "path/to/data.json", - enclave: kurtosis.DefaultEnclave, - kurtosisPackage: kurtosis.DefaultPackageName, - }, - wantError: false, - }, - { - name: "with autofix true", - args: []string{ - "--template", "path/to/template.yaml", - "--autofix", "true", - }, - wantCfg: &config{ - templateFile: "path/to/template.yaml", - enclave: kurtosis.DefaultEnclave, - kurtosisPackage: kurtosis.DefaultPackageName, - autofix: "true", - }, - wantError: false, - }, - { - name: "with autofix nuke", - args: []string{ - "--template", "path/to/template.yaml", - "--autofix", "nuke", - }, - wantCfg: &config{ - templateFile: "path/to/template.yaml", - enclave: kurtosis.DefaultEnclave, - kurtosisPackage: kurtosis.DefaultPackageName, - autofix: "nuke", - }, - wantError: false, - }, - { - name: "with invalid autofix value", - args: []string{ - "--template", "path/to/template.yaml", - "--autofix", "invalid", - }, - wantCfg: &config{ - templateFile: "path/to/template.yaml", - enclave: kurtosis.DefaultEnclave, - kurtosisPackage: kurtosis.DefaultPackageName, - autofix: "invalid", - }, - wantError: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var cfg *config - app := &cli.App{ - Flags: getFlags(), - Action: func(c *cli.Context) (err error) { - cfg, err = newConfig(c) - return - }, - } - - // Prepend program name to args as urfave/cli expects - args := append([]string{"prog"}, tt.args...) - - err := app.Run(args) - if tt.wantError { - assert.Error(t, err) - return - } - - require.NoError(t, err) - require.NotNil(t, cfg) - assert.Equal(t, tt.wantCfg.templateFile, cfg.templateFile) - assert.Equal(t, tt.wantCfg.enclave, cfg.enclave) - assert.Equal(t, tt.wantCfg.kurtosisPackage, cfg.kurtosisPackage) - if tt.wantCfg.dataFile != "" { - assert.Equal(t, tt.wantCfg.dataFile, cfg.dataFile) - } - if tt.wantCfg.autofix != "" { - assert.Equal(t, tt.wantCfg.autofix, cfg.autofix) - } - }) - } -} - -func TestMainFuncValidatesConfig(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "main-test") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - // Create test template - templatePath := filepath.Join(tmpDir, "template.yaml") - err = os.WriteFile(templatePath, []byte("name: test"), 0644) - require.NoError(t, err) - - // Create environment output path - envPath := filepath.Join(tmpDir, "env.json") - - app := &cli.App{ - Flags: getFlags(), - Action: func(c *cli.Context) error { - cfg, err := newConfig(c) - if err != nil { - return err - } - - // Verify config values - assert.Equal(t, templatePath, cfg.templateFile) - assert.Equal(t, envPath, cfg.environment) - assert.True(t, cfg.dryRun) - - // Create an empty environment file to simulate successful deployment - return os.WriteFile(envPath, []byte("{}"), 0644) - }, - } - - args := []string{ - "prog", - "--template", templatePath, - "--environment", envPath, - "--dry-run", - } - - err = app.Run(args) - require.NoError(t, err) - - // Verify the environment file was created - assert.FileExists(t, envPath) -} - -func TestAutofixModes(t *testing.T) { - tests := []struct { - name string - autofixEnv string - autofixFlag string - expectedMode autofixTypes.AutofixMode - }{ - { - name: "autofix disabled", - autofixEnv: "", - autofixFlag: "", - expectedMode: autofixTypes.AutofixModeDisabled, - }, - { - name: "autofix normal mode via env", - autofixEnv: "true", - autofixFlag: "", - expectedMode: autofixTypes.AutofixModeNormal, - }, - { - name: "autofix nuke mode via env", - autofixEnv: "nuke", - autofixFlag: "", - expectedMode: autofixTypes.AutofixModeNuke, - }, - { - name: "autofix normal mode via flag", - autofixEnv: "", - autofixFlag: "true", - expectedMode: autofixTypes.AutofixModeNormal, - }, - { - name: "autofix nuke mode via flag", - autofixEnv: "", - autofixFlag: "nuke", - expectedMode: autofixTypes.AutofixModeNuke, - }, - { - name: "flag takes precedence over env", - autofixEnv: "true", - autofixFlag: "nuke", - expectedMode: autofixTypes.AutofixModeNuke, - }, - { - name: "invalid autofix value", - autofixEnv: "invalid", - autofixFlag: "", - expectedMode: autofixTypes.AutofixModeDisabled, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "autofix-test") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - // Create test template - templatePath := filepath.Join(tmpDir, "template.yaml") - err = os.WriteFile(templatePath, []byte("name: test"), 0644) - require.NoError(t, err) - - // Create environment output path - envPath := filepath.Join(tmpDir, "env.json") - - // Set up test environment - if tt.autofixEnv != "" { - t.Setenv("AUTOFIX", tt.autofixEnv) - } - - app := &cli.App{ - Flags: getFlags(), - Action: func(c *cli.Context) error { - cfg, err := newConfig(c) - if err != nil { - return err - } - - // Verify autofix mode - autofixMode := autofixTypes.AutofixModeDisabled - if cfg.autofix == "true" { - autofixMode = autofixTypes.AutofixModeNormal - } else if cfg.autofix == "nuke" { - autofixMode = autofixTypes.AutofixModeNuke - } else if os.Getenv("AUTOFIX") == "true" { - autofixMode = autofixTypes.AutofixModeNormal - } else if os.Getenv("AUTOFIX") == "nuke" { - autofixMode = autofixTypes.AutofixModeNuke - } - assert.Equal(t, tt.expectedMode, autofixMode) - - // Create an empty environment file to simulate successful deployment - return os.WriteFile(envPath, []byte("{}"), 0644) - }, - } - - args := []string{ - "prog", - "--template", templatePath, - "--environment", envPath, - } - if tt.autofixFlag != "" { - args = append(args, "--autofix", tt.autofixFlag) - } - - err = app.Run(args) - require.NoError(t, err) - - // Verify the environment file was created - assert.FileExists(t, envPath) - }) - } -} diff --git a/kurtosis-devnet/fileserver/kurtosis.yml b/kurtosis-devnet/fileserver/kurtosis.yml deleted file mode 100644 index 2f1eb17b25e8d..0000000000000 --- a/kurtosis-devnet/fileserver/kurtosis.yml +++ /dev/null @@ -1,4 +0,0 @@ -name: github.com/ethereum-optimism/optimism/kurtosis-devnet/fileserver -description: |- - Kurtosis package for serving files from the build directory -replace: {} diff --git a/kurtosis-devnet/fileserver/main.star b/kurtosis-devnet/fileserver/main.star deleted file mode 100644 index 94b5dff887dde..0000000000000 --- a/kurtosis-devnet/fileserver/main.star +++ /dev/null @@ -1,53 +0,0 @@ -FILESERVER_HTTP_PORT_ID = "http" -FILESERVER_HTTP_PORT_NUM = 80 -FILESERVER_IMAGE = "nginx:latest" - - -def get_used_ports(): - used_ports = { - FILESERVER_HTTP_PORT_ID: PortSpec( - number=FILESERVER_HTTP_PORT_NUM, - ) - } - return used_ports - - -def run(plan, source_path, server_image=FILESERVER_IMAGE): - service_name = "fileserver" - config = get_fileserver_config( - plan = plan, - service_name = service_name, - source_path = source_path, - server_image = server_image, - ) - plan.add_service( - name = service_name, - config = config, - ) - return service_name - - -def get_fileserver_config(plan, service_name, source_path, server_image): - files = {} - - # Upload content to container - content_artifact = plan.upload_files( - src=source_path, - name="{}-content".format(service_name), - ) - files["/content"] = content_artifact - - # Add nginx config file - nginx_conf = plan.upload_files( - src="static_files/nginx", - name="{}-nginx-conf".format(service_name), - ) - files["/etc/nginx/conf.d"] = nginx_conf - - ports = get_used_ports() - return ServiceConfig( - image=server_image, - ports=ports, - cmd=["nginx", "-g", "daemon off;"], - files=files, - ) diff --git a/kurtosis-devnet/fileserver/static_files/nginx/default.conf b/kurtosis-devnet/fileserver/static_files/nginx/default.conf deleted file mode 100644 index 69932eb89d887..0000000000000 --- a/kurtosis-devnet/fileserver/static_files/nginx/default.conf +++ /dev/null @@ -1,8 +0,0 @@ -server { - listen 80; - server_name _; - root /content; - location / { - try_files $uri $uri/ =404; - } -} diff --git a/kurtosis-devnet/flash.yaml b/kurtosis-devnet/flash.yaml deleted file mode 100644 index b34eea0df02e1..0000000000000 --- a/kurtosis-devnet/flash.yaml +++ /dev/null @@ -1,110 +0,0 @@ -optimism_package: - faucet: - enabled: true - image: {{ localDockerImage "op-faucet" }} - chains: - op-kurtosis: - participants: - node0: - sequencer: true - el: - type: op-geth - el_builder: - type: op-rbuilder - cl_builder: - type: op-node - image: {{ localDockerImage "op-node" }} - mev_params: - enabled: true - cl: - type: op-node - image: {{ localDockerImage "op-node" }} - conductor_params: - image: {{ localDockerImage "op-conductor" }} - enabled: true - bootstrap: true - paused: true - admin: true - proxy: true - websocket_enabled: true - - node1: - sequencer: true - el: - type: op-reth - el_builder: - type: op-rbuilder - cl_builder: - type: op-node - image: {{ localDockerImage "op-node" }} - mev_params: - enabled: true - cl: - type: op-node - image: {{ localDockerImage "op-node" }} - conductor_params: - image: {{ localDockerImage "op-conductor" }} - enabled: true - paused: true - admin: true - proxy: true - websocket_enabled: true - - proxyd_params: - pprof_enabled: false - extra_params: [] - network_params: - network: "kurtosis" - network_id: "2151908" - seconds_per_slot: 2 - fjord_time_offset: 0 - granite_time_offset: 0 - holocene_time_offset: 0 - fund_dev_accounts: true - - flashblocks_websocket_proxy_params: - enabled: true - flashblocks_rpc_params: - type: op-reth - batcher_params: - image: {{ localDockerImage "op-batcher" }} - extra_params: [] - proposer_params: - image: {{ localDockerImage "op-proposer" }} - extra_params: [] - game_type: 1 - proposal_interval: 10m - challengers: - challenger: - enabled: true - image: {{ localDockerImage "op-challenger" }} - participants: "*" - cannon_prestates_url: {{ localPrestate.URL }} - cannon_trace_types: ["cannon", "permissioned"] - op_contract_deployer_params: - image: {{ localDockerImage "op-deployer" }} - l1_artifacts_locator: {{ localContractArtifacts "l1" }} - l2_artifacts_locator: {{ localContractArtifacts "l2" }} - overrides: - faultGameAbsolutePrestate: {{ localPrestate.Hashes.prestate_mt64 }} - global_log_level: "info" - global_node_selectors: {} - global_tolerations: [] - persistent: false -ethereum_package: - participants: - - el_type: geth - cl_type: teku - cl_image: consensys/teku:25.7.1 - network_params: - preset: minimal - genesis_delay: 5 - additional_preloaded_contracts: | - { - "0x4e59b44847b379578588920cA78FbF26c0B4956C": { - "balance": "0ETH", - "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", - "storage": {}, - "nonce": "1" - } - } diff --git a/kurtosis-devnet/foo-user.example.json b/kurtosis-devnet/foo-user.example.json deleted file mode 100644 index 7f7f5336d79ad..0000000000000 --- a/kurtosis-devnet/foo-user.example.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "interop": true, - "l2s": { - "2151908": { - "nodes": ["op-geth", "op-geth"] - }, - "2151909": { - "nodes": ["op-reth"] - } - }, - "overrides": { - "flags": { - "log_level": "--log.level=debug" - } - } -} \ No newline at end of file diff --git a/kurtosis-devnet/interop.yaml b/kurtosis-devnet/interop.yaml deleted file mode 100644 index 2295c963c6bb8..0000000000000 --- a/kurtosis-devnet/interop.yaml +++ /dev/null @@ -1,174 +0,0 @@ -{{- $local_images := dict - "op_node" (localDockerImage "op-node") - "op_batcher" (localDockerImage "op-batcher") - "op_challenger" (localDockerImage "op-challenger") - "op_conductor" (localDockerImage "op-conductor") - "op_proposer" (localDockerImage "op-proposer") - "op_deployer" (localDockerImage "op-deployer") - "op_supervisor" (localDockerImage "op-supervisor") - "op_faucet" (localDockerImage "op-faucet") - "op_interop_mon" (localDockerImage "op-interop-mon") --}} -{{- $urls := dict - "prestate" (localPrestate.URL) - "l1_artifacts" (localContractArtifacts "l1") - "l2_artifacts" (localContractArtifacts "l2") --}} -{{- $flags := dict - "log_level" "--log.level=info" - "log_format" "--log.format=logfmtms" - "interop_mempool_filtering" "--rollup.interopmempoolfiltering" - "experimental_sequencer_api" "--experimental.sequencer-api" --}} ---- -optimism_package: - faucet: - enabled: true - image: {{ $local_images.op_faucet }} - interop_mon: - enabled: true - image: {{ $local_images.op_interop_mon }} - superchains: - superchain: - enabled: true - supervisors: - supervisor: - superchain: superchain - image: {{ $local_images.op_supervisor }} - extra_params: - - {{ $flags.log_level }} - - {{ $flags.log_format }} - test-sequencers: - sequencer: - enabled: true - chains: - op-kurtosis1: - participants: - node0: &x-node - el: - type: op-geth - image: "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101602.1-rc.1" - log_level: "" - extra_env_vars: {} - extra_labels: {} - extra_params: - - {{ $flags.interop_mempool_filtering }} - tolerations: [] - volume_size: 0 - min_cpu: 0 - max_cpu: 0 - min_mem: 0 - max_mem: 0 - cl: - type: op-node - image: {{ $local_images.op_node }} - log_level: "" - extra_env_vars: {} - extra_labels: {} - extra_params: - - {{ $flags.log_format }} - - {{ $flags.experimental_sequencer_api }} - tolerations: [] - volume_size: 0 - min_cpu: 0 - max_cpu: 0 - min_mem: 0 - max_mem: 0 - mev_params: - image: "" - builder_host: "" - builder_port: "" - network_params: - network: "kurtosis" - network_id: "2151908" - seconds_per_slot: 2 - fjord_time_offset: 0 - granite_time_offset: 0 - holocene_time_offset: 0 - isthmus_time_offset: 0 - jovian_time_offset: 0 - interop_time_offset: 0 - fund_dev_accounts: true - batcher_params: - image: {{ $local_images.op_batcher }} - extra_params: - - {{ $flags.log_level }} - - {{ $flags.log_format }} - proposer_params: - image: {{ $local_images.op_proposer }} - extra_params: - - {{ $flags.log_level }} - - {{ $flags.log_format }} - game_type: 1 - proposal_interval: 10m - op-kurtosis2: - participants: - node0: *x-node - network_params: - network: "kurtosis" - network_id: "2151909" - seconds_per_slot: 2 - fjord_time_offset: 0 - granite_time_offset: 0 - holocene_time_offset: 0 - isthmus_time_offset: 0 - jovian_time_offset: 0 - interop_time_offset: 0 - fund_dev_accounts: true - batcher_params: - image: {{ $local_images.op_batcher }} - extra_params: - - {{ $flags.log_level }} - - {{ $flags.log_format }} - proposer_params: - image: {{ $local_images.op_proposer }} - extra_params: - - {{ $flags.log_level }} - - {{ $flags.log_format }} - game_type: 1 - proposal_interval: 10m - challengers: - challenger: - enabled: true - image: {{ $local_images.op_challenger }} - participants: "*" - cannon_prestates_url: {{ localPrestate.URL }} - cannon_trace_types: ["super-cannon", "super-permissioned"] - extra_params: - - {{ $flags.log_level }} - - {{ $flags.log_format }} - op_contract_deployer_params: - image: {{ $local_images.op_deployer }} - l1_artifacts_locator: {{ $urls.l1_artifacts }} - l2_artifacts_locator: {{ $urls.l2_artifacts }} - overrides: - faultGameAbsolutePrestate: {{ localPrestate.Hashes.prestate_interop }} - global_log_level: "info" - global_node_selectors: {} - global_tolerations: [] - persistent: false - observability: - grafana_params: - dashboard_sources: - - github.com/ethereum-optimism/grafana-dashboards-public/resources - - github.com/op-rs/kona/docker/recipes/kona-node/grafana - - github.com/paradigmxyz/reth/etc/grafana - - github.com/geoknee/grafana-dashboards/ - - github.com/nonsense/op-stack-grafana-dashboards/resources -ethereum_package: - participants: - - el_type: geth - cl_type: teku - cl_image: consensys/teku:25.7.1 - network_params: - preset: minimal - genesis_delay: 5 - additional_preloaded_contracts: | - { - "0x4e59b44847b379578588920cA78FbF26c0B4956C": { - "balance": "0ETH", - "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", - "storage": {}, - "nonce": "1" - } - } diff --git a/kurtosis-devnet/jovian.yaml b/kurtosis-devnet/jovian.yaml deleted file mode 100644 index a0ab9bcb1b9c8..0000000000000 --- a/kurtosis-devnet/jovian.yaml +++ /dev/null @@ -1,86 +0,0 @@ -optimism_package: - faucet: - enabled: true - image: {{ localDockerImage "op-faucet" }} - chains: - op-kurtosis: - participants: - node0: - el: - type: op-geth - image: "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101602.1-rc.1" - log_level: "" - extra_env_vars: {} - extra_labels: {} - extra_params: [] - tolerations: [] - volume_size: 0 - min_cpu: 0 - max_cpu: 0 - min_mem: 0 - max_mem: 0 - cl: &x-node-cl - type: op-node - image: {{ localDockerImage "op-node" }} - log_level: "" - extra_env_vars: {} - extra_labels: {} - extra_params: [] - tolerations: [] - volume_size: 0 - min_cpu: 0 - max_cpu: 0 - min_mem: 0 - max_mem: 0 - network_params: - network: "kurtosis" - network_id: "2151908" - seconds_per_slot: 2 - fjord_time_offset: 0 - granite_time_offset: 0 - holocene_time_offset: 0 - isthmus_time_offset: 0 - jovian_time_offset: 0 - fund_dev_accounts: true - batcher_params: - image: {{ localDockerImage "op-batcher" }} - extra_params: [] - proposer_params: - image: {{ localDockerImage "op-proposer" }} - extra_params: [] - game_type: 1 - proposal_interval: 10m - challengers: - challenger: - enabled: true - image: {{ localDockerImage "op-challenger" }} - participants: "*" - cannon_prestates_url: {{ localPrestate.URL }} - cannon_trace_types: ["cannon", "permissioned"] - op_contract_deployer_params: - image: {{ localDockerImage "op-deployer" }} - l1_artifacts_locator: {{ localContractArtifacts "l1" }} - l2_artifacts_locator: {{ localContractArtifacts "l2" }} - overrides: - faultGameAbsolutePrestate: {{ localPrestate.Hashes.prestate_mt64 }} - global_log_level: "info" - global_node_selectors: {} - global_tolerations: [] - persistent: false -ethereum_package: - participants: - - el_type: geth - cl_type: teku - cl_image: consensys/teku:25.7.1 - network_params: - preset: minimal - genesis_delay: 5 - additional_preloaded_contracts: | - { - "0x4e59b44847b379578588920cA78FbF26c0B4956C": { - "balance": "0ETH", - "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", - "storage": {}, - "nonce": "1" - } - } diff --git a/kurtosis-devnet/justfile b/kurtosis-devnet/justfile deleted file mode 100644 index 9ba597891a525..0000000000000 --- a/kurtosis-devnet/justfile +++ /dev/null @@ -1,101 +0,0 @@ -import '../justfiles/prerequisites.just' - -KURTOSIS_PACKAGE := "./optimism-package-trampoline/" - -test: _prerequisites - go test --tags=testonly ./... - -_kurtosis-run PACKAGE_NAME ARG_FILE ENCLAVE: - kurtosis run {{PACKAGE_NAME}} --args-file {{ARG_FILE}} --enclave {{ENCLAVE}} --show-enclave-inspect=false --image-download=missing - -_prestate-build PATH='.': - docker buildx build --output {{PATH}} --progress plain -f ../op-program/Dockerfile.repro ../ - -_docker_build TAG TARGET CONTEXT DOCKERFILE *ARGS: _prerequisites - #!/usr/bin/env bash - # --load is needed to ensure the image ends up in the local registry - # --provenance=false is needed to make the build idempotent - docker buildx build \ - --load \ - --provenance=false \ - -t {{TAG}} \ - -f {{CONTEXT}}/{{DOCKERFILE}} \ - {{ if TARGET != '' { "--target " + TARGET } else { "" } }} \ - --build-arg GIT_COMMIT={git_commit} \ - --build-arg GIT_DATE={git_date} \ - {{ ARGS }} \ - {{CONTEXT}} - -_docker_build_stack TAG TARGET *ARGS: (_docker_build TAG TARGET "../" "ops/docker/op-stack-go/Dockerfile" ARGS) - -cannon-image TAG='cannon:devnet': (_docker_build_stack TAG "cannon-target") -da-server-image TAG='da-server:devnet': (_docker_build_stack TAG "da-server-target") -op-batcher-image TAG='op-batcher:devnet': (_docker_build_stack TAG "op-batcher-target") -op-challenger-image TAG='op-challenger:devnet': (_docker_build_stack TAG "op-challenger-target") -op-conductor-image TAG='op-conductor:devnet': (_docker_build_stack TAG "op-conductor-target") -op-deployer-image TAG='op-deployer:devnet': (_docker_build_stack TAG "op-deployer-target") -op-dispute-mon-image TAG='op-dispute-mon:devnet': (_docker_build_stack TAG "op-dispute-mon-target") -op-node-image TAG='op-node:devnet': (_docker_build_stack TAG "op-node-target") -op-program-image TAG='op-program:devnet': (_docker_build_stack TAG "op-program-target") -op-proposer-image TAG='op-proposer:devnet': (_docker_build_stack TAG "op-proposer-target") -op-supervisor-image TAG='op-supervisor:devnet': (_docker_build_stack TAG "op-supervisor-target") -op-wheel-image TAG='op-wheel:devnet': (_docker_build_stack TAG "op-wheel-target") -op-faucet-image TAG='op-faucet:devnet': (_docker_build_stack TAG "op-faucet-target") -op-interop-mon-image TAG='op-interop-mon:devnet': (_docker_build_stack TAG "op-interop-mon-target") - -op-program-builder-image TAG='op-program-builder:devnet': _prerequisites - just op-program-svc/op-program-svc {{TAG}} - - -# Devnet template recipe -devnet TEMPLATE_FILE DATA_FILE="" NAME="" PACKAGE=KURTOSIS_PACKAGE: _prerequisites - #!/usr/bin/env bash - export DEVNET_NAME={{NAME}} - if [ -z "{{NAME}}" ]; then - export DEVNET_NAME=`basename {{TEMPLATE_FILE}} .yaml` - if [ -n "{{DATA_FILE}}" ]; then - export DATA_FILE_NAME=`basename {{DATA_FILE}} .json` - export DEVNET_NAME="$DEVNET_NAME-$DATA_FILE_NAME" - fi - fi - export ENCL_NAME="$DEVNET_NAME"-devnet - export CONDUCTOR_CONFIG="tests/op-conductor-ops-$ENCL_NAME.toml" - go run cmd/main.go -kurtosis-package {{PACKAGE}} \ - -environment "tests/$ENCL_NAME.json" \ - -conductor-config "$CONDUCTOR_CONFIG" \ - -template "{{TEMPLATE_FILE}}" \ - -data "{{DATA_FILE}}" \ - -enclave "$ENCL_NAME" \ - && cat "tests/$ENCL_NAME.json" && if [ -f "$CONDUCTOR_CONFIG" ]; then cat "$CONDUCTOR_CONFIG"; fi - -devnet-test DEVNET *TEST: _prerequisites - #!/usr/bin/env bash - export TESTS=({{TEST}}) - # we need a timestamp in there to force kurtosis to not cache the test solely based on its name! - export ARGS=$(printf '%s\n' "${TESTS[@]}" | jq -R . | jq -s . | jq -s '{devnet: "{{DEVNET}}", timestamp: "{{datetime("%s")}}", tests: add}') - kurtosis run --enclave {{DEVNET}} \ - --show-enclave-inspect=false \ - ./tests/ "$ARGS" - -# Devnet recipes - -# Simple devnet -simple-devnet: (devnet "simple.yaml") - -# Interop devnet -interop-devnet: (devnet "interop.yaml") -interop-devnet-test: (devnet-test "interop-devnet" "interop-smoke-test.sh") - -# User devnet -user-devnet DATA_FILE: - {{just_executable()}} devnet "user.yaml" {{DATA_FILE}} {{file_stem(DATA_FILE)}} - -# Jovian devnet -jovian-devnet: (devnet "jovian.yaml") - -# Flashblocks devnet -flash-devnet: (devnet "flash.yaml") - -# subshells -enter-devnet DEVNET CHAIN='Ethereum' NODE_INDEX='0': _prerequisites - go run ../devnet-sdk/shell/cmd/enter/main.go --devnet kt://{{DEVNET}} --chain {{CHAIN}} --node-index {{NODE_INDEX}} diff --git a/kurtosis-devnet/op-program-svc/Dockerfile b/kurtosis-devnet/op-program-svc/Dockerfile deleted file mode 100644 index 3f11dbb3c113d..0000000000000 --- a/kurtosis-devnet/op-program-svc/Dockerfile +++ /dev/null @@ -1,38 +0,0 @@ -ARG BASE_IMAGE=op-program-base:latest - -FROM golang:1.24.10-alpine3.20 AS builder - -COPY ./*.go /app/ -WORKDIR /app - -RUN go mod init op-program-svc -RUN go build -o op-program-svc . - - -FROM ${BASE_IMAGE} AS svc - -ARG GIT_COMMIT -ARG GIT_DATE - -ARG CANNON_VERSION=v0.0.0 -ARG OP_PROGRAM_VERSION=v0.0.0 - -ARG TARGETOS TARGETARCH - -WORKDIR /app - -# build cannon ahead of time -RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build just \ - -d /app/op-program \ - -f /app/op-program/repro.justfile \ - GOOS="$TARGETOS" \ - GOARCH="$TARGETARCH" \ - GIT_COMMIT="$GIT_COMMIT" \ - GIT_DATE="$GIT_DATE" \ - CANNON_VERSION="$CANNON_VERSION" \ - OP_PROGRAM_VERSION="$OP_PROGRAM_VERSION" \ - cannon - -COPY --from=builder /app/op-program-svc . -EXPOSE 8080 -CMD ["./op-program-svc"] diff --git a/kurtosis-devnet/op-program-svc/README.md b/kurtosis-devnet/op-program-svc/README.md deleted file mode 100644 index 091654c0d9ac4..0000000000000 --- a/kurtosis-devnet/op-program-svc/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# op-program-svc - -This small service is a temporary measure until we come up with a better way -of generating/serving prestate files based on chain information. - -# API - -The API is intentionally extremely simple: -- `POST /`: generate new prestates from provided inputs -- `GET /HASH.(bin.gz|json)`: get prestate data -- `GET /info.json`: get prestates mapping - -The idea is for this service to be basically a function -(chains_specs, deptsets) -> prestates. - -In the future, we definitely want to replace the implementation of that -function (see implementation notes below) - -## Trigger new build: - -Example using curl - -``` -$ curl -X POST -H "Content-Type: multipart/form-data" \ - -F "files[]=@rollup-2151908.json" \ - -F "files[]=@rollup-2151909.json" \ - -F "files[]=@genesis-2151908.json" \ - -F "files[]=@genesis-2151909.json" \ - -F "files[]=@depsets.json" \ - http://localhost:8080 -``` - -## Retrieve prestates mapping - -``` -$ curl -q http://localhost:8080/info.json -{ - "prestate_interop": "0x034731331d519c93fc0562643e0728c43f8e45a0af1160ad4c57c4e5141d2bbb", - "prestate_mt64": "0x0325bb0ca8521b468bb8234d8ba54b1b74db60e2b5bc75d0077a0fe2098b6b45" -} -``` - -## Implementation notes - -Unfortunately, op-program-client relies on embedded (using `//go:embed`) -configuration files to store unannounced chain configs. - -This means that in the context of devnets, we need to store the configs -(which are available only mid-deployment) into the **source tree** and -trigger a late build step. - -So effectively, we need to package the relevant part of the sources into -a container, deploy that one alongside the devnet, and run that build step -on demand. - -This is ugly, unsafe, easy to run a DOS against,... we need to do better. -But for now this is what we have. diff --git a/kurtosis-devnet/op-program-svc/build.go b/kurtosis-devnet/op-program-svc/build.go deleted file mode 100644 index 7da7a4a23d87b..0000000000000 --- a/kurtosis-devnet/op-program-svc/build.go +++ /dev/null @@ -1,174 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "io" - "log" - "mime/multipart" - "path/filepath" - "strconv" - "strings" - "sync" - - "bufio" -) - -// MultipartUploadedFile adapts multipart.FileHeader to UploadedFile -type MultipartUploadedFile struct { - header *multipart.FileHeader -} - -func NewMultipartUploadedFile(header *multipart.FileHeader) *MultipartUploadedFile { - return &MultipartUploadedFile{header: header} -} - -func (f *MultipartUploadedFile) Open() (io.ReadCloser, error) { - return f.header.Open() -} - -func (f *MultipartUploadedFile) GetFilename() string { - return f.header.Filename -} - -type Builder struct { - appRoot string - configsDir string - buildDir string - buildCmd string - fs FS - cmdFactory CommandFactory -} - -func NewBuilder(appRoot, configsDir, buildDir, buildCmd string) *Builder { - return &Builder{ - appRoot: appRoot, - configsDir: configsDir, - buildDir: buildDir, - buildCmd: buildCmd, - fs: &DefaultFileSystem{}, - cmdFactory: &DefaultCommandFactory{}, - } -} - -func (b *Builder) SaveUploadedFiles(files []UploadedFile) error { - // Create configs directory if it doesn't exist - fullConfigsDir := b.fs.Join(b.appRoot, b.buildDir, b.configsDir) - if err := b.fs.MkdirAll(fullConfigsDir, 0755); err != nil { - return fmt.Errorf("failed to create config directory: %w", err) - } - - // Save the files - for _, file := range files { - reader, err := file.Open() - if err != nil { - return fmt.Errorf("failed to open file: %w", err) - } - defer reader.Close() - - destPath := b.fs.Join(fullConfigsDir, b.normalizeFilename(file.GetFilename())) - dst, err := b.fs.Create(destPath) - if err != nil { - return fmt.Errorf("failed to create destination file: %w", err) - } - defer dst.Close() - - if _, err := io.Copy(dst, reader); err != nil { - return fmt.Errorf("failed to save file: %w", err) - } - log.Printf("Saved file: %s", destPath) - } - - return nil -} - -func (b *Builder) ExecuteBuild() ([]byte, error) { - log.Printf("Starting build...") - cmdParts := strings.Fields(b.buildCmd) - cmd := b.cmdFactory.CreateCommand(cmdParts[0], cmdParts[1:]...) - - // Set working directory - cmd.SetDir(b.fs.Join(b.appRoot, b.buildDir)) - - // Create pipes for stdout and stderr - stdout, err := cmd.StdoutPipe() - if err != nil { - return nil, fmt.Errorf("failed to create stdout pipe: %w", err) - } - stderr, err := cmd.StderrPipe() - if err != nil { - return nil, fmt.Errorf("failed to create stderr pipe: %w", err) - } - - // Buffer to store complete output for error reporting - var output bytes.Buffer - output.WriteString("Build output:\n") - - // Start the command - if err := cmd.Start(); err != nil { - return nil, fmt.Errorf("failed to start build: %w", err) - } - - // Create a WaitGroup to wait for both stdout and stderr to be processed - var wg sync.WaitGroup - wg.Add(2) - - // Stream stdout - go func() { - defer wg.Done() - scanner := bufio.NewScanner(stdout) - for scanner.Scan() { - line := scanner.Text() - log.Printf("[build] %s", line) - output.WriteString(line + "\n") - } - }() - - // Stream stderr - go func() { - defer wg.Done() - scanner := bufio.NewScanner(stderr) - for scanner.Scan() { - line := scanner.Text() - log.Printf("[build][stderr] %s", line) - output.WriteString(line + "\n") - } - }() - - // Wait for both streams to complete - wg.Wait() - - // Wait for the command to complete - if err := cmd.Wait(); err != nil { - return output.Bytes(), fmt.Errorf("build failed: %w", err) - } - - log.Printf("Build completed successfully") - return output.Bytes(), nil -} - -// This is a convenience hack to natively support the file format of op-deployer -func (b *Builder) normalizeFilename(filename string) string { - // Get just the filename without directories - filename = filepath.Base(filename) - - // Check if filename matches PREFIX-NUMBER.json pattern - if parts := strings.Split(filename, "-"); len(parts) == 2 { - if numStr := strings.TrimSuffix(parts[1], ".json"); numStr != parts[1] { - // Check if the number part is actually numeric - if _, err := strconv.Atoi(numStr); err == nil { - // Handle specific cases - switch parts[0] { - case "genesis": - return fmt.Sprintf("%s-genesis-l2.json", numStr) - case "rollup": - return fmt.Sprintf("%s-rollup.json", numStr) - - } - // For all other cases, leave the filename unchanged - } - } - } - - return filename -} diff --git a/kurtosis-devnet/op-program-svc/build_test.go b/kurtosis-devnet/op-program-svc/build_test.go deleted file mode 100644 index 70a813f1f3b39..0000000000000 --- a/kurtosis-devnet/op-program-svc/build_test.go +++ /dev/null @@ -1,211 +0,0 @@ -package main - -import ( - "strings" - "testing" -) - -func TestSaveUploadedFiles(t *testing.T) { - tests := []struct { - name string - files []struct { - filename string - content []byte - } - shouldFail bool - }{ - { - name: "successful save", - files: []struct { - filename string - content []byte - }{ - { - filename: "test1.json", - content: []byte("test1 content"), - }, - { - filename: "test2.json", - content: []byte("test2 content"), - }, - }, - shouldFail: false, - }, - { - name: "filesystem error", - files: []struct { - filename string - content []byte - }{ - { - filename: "test1.json", - content: []byte("test1 content"), - }, - }, - shouldFail: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockFS := NewMockFS() - mockFS.ShouldFail = tt.shouldFail - - // Create mock uploaded files - files := make([]UploadedFile, len(tt.files)) - for i, f := range tt.files { - files[i] = NewMockUploadedFile(f.filename, f.content) - } - - b := &Builder{ - appRoot: "app", - configsDir: "configs", - buildDir: "build", - fs: mockFS, - } - - err := b.SaveUploadedFiles(files) - - if tt.shouldFail && err == nil { - t.Error("expected error but got none") - } - - if !tt.shouldFail { - if err != nil { - t.Errorf("unexpected error: %v", err) - } - - // Verify correct directory was created - if len(mockFS.MkdirCalls) != 1 { - t.Errorf("expected 1 mkdir call, got %d", len(mockFS.MkdirCalls)) - } - - // Verify files were created - expectedCreateCalls := len(tt.files) - if len(mockFS.CreateCalls) != expectedCreateCalls { - t.Errorf("expected %d create calls, got %d", expectedCreateCalls, len(mockFS.CreateCalls)) - } - } - }) - } -} - -func TestExecuteBuild(t *testing.T) { - tests := []struct { - name string - stdout string - stderr string - shouldFail bool - }{ - { - name: "successful build", - stdout: "build successful\n", - stderr: "", - shouldFail: false, - }, - { - name: "build failure", - stdout: "", - stderr: "build failed\n", - shouldFail: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockRunner := &MockCommandRunner{ - ShouldFail: tt.shouldFail, - Stdout: tt.stdout, - Stderr: tt.stderr, - } - - mockFactory := &MockCommandFactory{ - Runner: mockRunner, - } - - mockFS := NewMockFS() - - b := &Builder{ - appRoot: "app", - buildDir: "build", - buildCmd: "make build", - cmdFactory: mockFactory, - fs: mockFS, - } - - output, err := b.ExecuteBuild() - - if tt.shouldFail && err == nil { - t.Error("expected error but got none") - } - - if !tt.shouldFail { - if err != nil { - t.Errorf("unexpected error: %v", err) - } - - if !mockRunner.StartCalled { - t.Error("Start was not called") - } - - if !mockRunner.WaitCalled { - t.Error("Wait was not called") - } - - if !strings.Contains(string(output), tt.stdout) { - t.Errorf("expected output to contain %q, got %q", tt.stdout, string(output)) - } - } - }) - } -} - -func TestNormalizeFilename(t *testing.T) { - tests := []struct { - name string - input string - expected string - }{ - { - name: "standard format - unchanged", - input: "prefix-123.json", - expected: "prefix-123.json", - }, - { - name: "genesis format", - input: "genesis-123.json", - expected: "123-genesis-l2.json", - }, - { - name: "rollup format", - input: "rollup-456.json", - expected: "456-rollup.json", - }, - { - name: "no number", - input: "test.json", - expected: "test.json", - }, - { - name: "invalid number", - input: "prefix-abc.json", - expected: "prefix-abc.json", - }, - { - name: "no json extension", - input: "prefix-123.txt", - expected: "prefix-123.txt", - }, - } - - b := &Builder{} - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := b.normalizeFilename(tt.input) - if result != tt.expected { - t.Errorf("expected %q, got %q", tt.expected, result) - } - }) - } -} diff --git a/kurtosis-devnet/op-program-svc/defaults.go b/kurtosis-devnet/op-program-svc/defaults.go deleted file mode 100644 index baf756a0d423d..0000000000000 --- a/kurtosis-devnet/op-program-svc/defaults.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "io" - "io/fs" - "os" - "os/exec" - "path/filepath" -) - -// osFile wraps os.File to implement File -type osFile struct { - *os.File -} - -func (f *osFile) Readdir(count int) ([]fs.FileInfo, error) { - return f.File.Readdir(count) -} - -// DefaultFileSystem implements testutils.FS using actual OS calls -type DefaultFileSystem struct{} - -func (fs *DefaultFileSystem) Open(name string) (File, error) { - file, err := os.Open(name) - if err != nil { - return nil, err - } - return &osFile{File: file}, nil -} - -func (fs *DefaultFileSystem) ReadDir(name string) ([]fs.DirEntry, error) { - return os.ReadDir(name) -} - -func (fs *DefaultFileSystem) ReadFile(name string) ([]byte, error) { - return os.ReadFile(name) -} - -func (fs *DefaultFileSystem) MkdirAll(path string, perm os.FileMode) error { - return os.MkdirAll(path, perm) -} - -func (fs *DefaultFileSystem) Create(name string) (io.WriteCloser, error) { - return os.Create(name) -} - -func (fs *DefaultFileSystem) Join(elem ...string) string { - return filepath.Join(elem...) -} - -func (fs *DefaultFileSystem) Stat(name string) (fs.FileInfo, error) { - return os.Stat(name) -} - -// commandWrapper wraps exec.Cmd to implement CommandRunner -type commandWrapper struct { - *exec.Cmd -} - -func (c *commandWrapper) SetDir(dir string) { - c.Cmd.Dir = dir -} - -// DefaultCommandFactory implements testutils.CommandFactory using actual OS exec -type DefaultCommandFactory struct{} - -func (f *DefaultCommandFactory) CreateCommand(name string, args ...string) CommandRunner { - cmd := exec.Command(name, args...) - return &commandWrapper{Cmd: cmd} -} diff --git a/kurtosis-devnet/op-program-svc/fs.go b/kurtosis-devnet/op-program-svc/fs.go deleted file mode 100644 index c79ef00773d69..0000000000000 --- a/kurtosis-devnet/op-program-svc/fs.go +++ /dev/null @@ -1,330 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "io/fs" - "log" - "net/http" - "os" - "path/filepath" - "regexp" - "strings" - "sync" - "time" -) - -const ( - depsetsFilename = "depsets.json" - infoFilename = "info.json" -) - -// proofFileSystem implements http.FileSystem, mapping hash-based virtual paths to actual files -type proofFileSystem struct { - root string - fs FS // Use our consolidated FS interface - proofFiles map[string]string // hash -> variable part mapping - proofMutex sync.RWMutex -} - -// proofFile implements http.File, representing a virtual file in our proof filesystem -type proofFile struct { - file File -} - -// infoFile implements http.File for the virtual info.json file -type infoFile struct { - *bytes.Reader - content []byte -} - -func newInfoFile(proofFiles map[string]string) *infoFile { - // Create inverted map - invertedMap := make(map[string]string) - for hash, variablePart := range proofFiles { - // Replace dashes with underscores in the key - key := fmt.Sprintf("prestate%s", variablePart) - key = strings.ReplaceAll(key, "-", "_") - invertedMap[key] = hash - } - - // Convert to JSON - content, err := json.MarshalIndent(invertedMap, "", " ") - if err != nil { - // Fallback to empty JSON object if marshaling fails - content = []byte("{}") - } - - return &infoFile{ - Reader: bytes.NewReader(content), - content: content, - } -} - -func (f *infoFile) Close() error { - return nil -} - -func (f *infoFile) Readdir(count int) ([]fs.FileInfo, error) { - return nil, fmt.Errorf("not a directory") -} - -func (f *infoFile) Stat() (fs.FileInfo, error) { - return virtualFileInfo{ - name: infoFilename, - size: int64(len(f.content)), - mode: 0644, - modTime: time.Now(), - isDir: false, - }, nil -} - -func (f *proofFile) Close() error { - return f.file.Close() -} - -func (f *proofFile) Read(p []byte) (n int, err error) { - return f.file.Read(p) -} - -func (f *proofFile) Seek(offset int64, whence int) (int64, error) { - return f.file.(io.Seeker).Seek(offset, whence) -} - -func (f *proofFile) Readdir(count int) ([]fs.FileInfo, error) { - // For actual files, we don't support directory listing - return nil, fmt.Errorf("not a directory") -} - -func (f *proofFile) Stat() (fs.FileInfo, error) { - return f.file.(fs.File).Stat() -} - -// proofDir implements http.File for the root directory -type proofDir struct { - *proofFileSystem - pos int -} - -func (d *proofDir) Close() error { - return nil -} - -func (d *proofDir) Read(p []byte) (n int, err error) { - return 0, fmt.Errorf("cannot read a directory") -} - -func (d *proofDir) Seek(offset int64, whence int) (int64, error) { - return 0, fmt.Errorf("cannot seek a directory") -} - -func (d *proofDir) Readdir(count int) ([]fs.FileInfo, error) { - d.proofMutex.RLock() - defer d.proofMutex.RUnlock() - - // Calculate total number of entries - totalEntries := len(d.proofFiles)*2 + 1 // hash.json, hash.bin.gz files + info.json - - // If we've already read all entries - if d.pos >= totalEntries { - if count <= 0 { - return nil, nil - } - return nil, io.EOF - } - - // Convert hashes to virtual file entries - var entries []fs.FileInfo - hashes := make([]string, 0, len(d.proofFiles)) - for hash := range d.proofFiles { - hashes = append(hashes, hash) - } - - start := d.pos - end := start + count - if count <= 0 || end > totalEntries { - end = totalEntries - } - - for i := start; i < end; i++ { - // Special case for info.json (second to last entry) - if i == len(d.proofFiles)*2 { - entries = append(entries, virtualFileInfo{ - name: infoFilename, - size: 0, // Size will be determined when actually opening the file - mode: 0644, - modTime: time.Now(), - isDir: false, - }) - continue - } - - hash := hashes[i/2] - isJSON := i%2 == 0 - - var name string - if isJSON { - name = hash + ".json" - } else { - name = hash + ".bin.gz" - } - - // Create a virtual file info - entries = append(entries, virtualFileInfo{ - name: name, - size: 0, // Size will be determined when actually opening the file - mode: 0644, - modTime: time.Now(), - isDir: false, - }) - } - - d.pos = end - return entries, nil -} - -func (d *proofDir) Stat() (fs.FileInfo, error) { - return virtualFileInfo{ - name: ".", - size: 0, - mode: 0755, - modTime: time.Now(), - isDir: true, - }, nil -} - -// virtualFileInfo implements fs.FileInfo for our virtual files -type virtualFileInfo struct { - name string - size int64 - mode fs.FileMode - modTime time.Time - isDir bool -} - -func (v virtualFileInfo) Name() string { return v.name } -func (v virtualFileInfo) Size() int64 { return v.size } -func (v virtualFileInfo) Mode() fs.FileMode { return v.mode } -func (v virtualFileInfo) ModTime() time.Time { return v.modTime } -func (v virtualFileInfo) IsDir() bool { return v.isDir } -func (v virtualFileInfo) Sys() interface{} { return nil } - -func newProofFileSystem(root string) *proofFileSystem { - return &proofFileSystem{ - root: root, - fs: &DefaultFileSystem{}, - proofFiles: make(map[string]string), - } -} - -// SetFS allows replacing the filesystem implementation, primarily for testing -func (fs *proofFileSystem) SetFS(newFS FS) { - fs.proofMutex.Lock() - defer fs.proofMutex.Unlock() - fs.fs = newFS -} - -func (fs *proofFileSystem) Open(name string) (http.File, error) { - if name == "/" || name == "" { - return &proofDir{proofFileSystem: fs}, nil - } - - // Clean the path and remove leading slash - name = strings.TrimPrefix(filepath.Clean(name), "/") - - // Special case for info.json - if name == infoFilename { - fs.proofMutex.RLock() - defer fs.proofMutex.RUnlock() - return newInfoFile(fs.proofFiles), nil - } - - fs.proofMutex.RLock() - defer fs.proofMutex.RUnlock() - - var targetFile string - if strings.HasSuffix(name, ".json") { - hash := strings.TrimSuffix(name, ".json") - if variablePart, ok := fs.proofFiles[hash]; ok { - targetFile = fmt.Sprintf("prestate-proof%s.json", variablePart) - } - } else if strings.HasSuffix(name, ".bin.gz") { - hash := strings.TrimSuffix(name, ".bin.gz") - if variablePart, ok := fs.proofFiles[hash]; ok { - targetFile = fmt.Sprintf("prestate%s.bin.gz", variablePart) - } - } - - if targetFile == "" { - return nil, fs.Error("file not found") - } - - file, err := fs.fs.Open(fs.fs.Join(fs.root, targetFile)) - if err != nil { - return nil, err - } - - return &proofFile{file: file}, nil -} - -func (fs *proofFileSystem) scanProofFiles() error { - fs.proofMutex.Lock() - defer fs.proofMutex.Unlock() - - // Clear existing mappings - fs.proofFiles = make(map[string]string) - - // Read directory entries - entries, err := fs.fs.ReadDir(fs.root) - if err != nil { - return fmt.Errorf("failed to read proofs directory: %w", err) - } - - // Regexp for matching prestate-proof files and extracting the variable part - proofRegexp := regexp.MustCompile(`^prestate-proof(.*)\.json$`) - - for _, entry := range entries { - log.Printf("entry: %s", entry.Name()) - if entry.IsDir() { - log.Printf("entry is a directory: %s", entry.Name()) - continue - } - - matches := proofRegexp.FindStringSubmatch(entry.Name()) - if matches == nil { - log.Printf("Warning: ignoring non-proof file %s", entry.Name()) - continue - } - - // matches[1] contains the variable part (including the leading hyphen if present) - variablePart := matches[1] - - // Read and parse the JSON file - data, err := fs.fs.ReadFile(fs.fs.Join(fs.root, entry.Name())) - if err != nil { - log.Printf("Warning: failed to read proof file %s: %v", entry.Name(), err) - continue - } - - var proofData struct { - Pre string `json:"pre"` - } - if err := json.Unmarshal(data, &proofData); err != nil { - log.Printf("Warning: failed to parse proof file %s: %v", entry.Name(), err) - continue - } - - // Store the mapping from hash to variable part of filename - fs.proofFiles[proofData.Pre] = variablePart - log.Printf("Mapped hash %s to proof file pattern%s", proofData.Pre, variablePart) - } - - return nil -} - -func (fs *proofFileSystem) Error(msg string) error { - return &os.PathError{Op: "open", Path: "virtual path", Err: errors.New(msg)} -} diff --git a/kurtosis-devnet/op-program-svc/fs_test.go b/kurtosis-devnet/op-program-svc/fs_test.go deleted file mode 100644 index 67a1f204dfb3d..0000000000000 --- a/kurtosis-devnet/op-program-svc/fs_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "io" - "testing" -) - -func TestProofFileSystem(t *testing.T) { - // Create mock filesystem - mockfs := NewMockFS() - - // Add test files - proofData := map[string]interface{}{ - "pre": "hash123", - } - proofJSON, _ := json.Marshal(proofData) - - mockfs.Files["/proofs/prestate-proof-test.json"] = NewMockFile( - "prestate-proof-test.json", - proofJSON, - ) - mockfs.Files["/proofs/prestate-test.bin.gz"] = NewMockFile( - "prestate-test.bin.gz", - []byte("mock binary data"), - ) - - // Create proof filesystem and set mock fs - pfs := newProofFileSystem("/proofs") - pfs.SetFS(mockfs) - - // Test scanning proof files - t.Run("ScanProofFiles", func(t *testing.T) { - err := pfs.scanProofFiles() - if err != nil { - t.Errorf("scanProofFiles failed: %v", err) - } - - // Verify mapping was created - if mapping, ok := pfs.proofFiles["hash123"]; !ok || mapping != "-test" { - t.Errorf("Expected mapping for hash123 to be -test, got %v", mapping) - } - }) - - t.Run("OpenJSONFile", func(t *testing.T) { - file, err := pfs.Open("/hash123.json") - if err != nil { - t.Errorf("Failed to open JSON file: %v", err) - } - defer file.Close() - - // Read contents - contents, err := io.ReadAll(file) - if err != nil { - t.Errorf("Failed to read file contents: %v", err) - } - - if !bytes.Equal(contents, proofJSON) { - t.Errorf("File contents don't match expected") - } - }) - - t.Run("OpenBinaryFile", func(t *testing.T) { - file, err := pfs.Open("/hash123.bin.gz") - if err != nil { - t.Errorf("Failed to open binary file: %v", err) - } - defer file.Close() - - contents, err := io.ReadAll(file) - if err != nil { - t.Errorf("Failed to read file contents: %v", err) - } - - if !bytes.Equal(contents, []byte("mock binary data")) { - t.Errorf("File contents don't match expected") - } - }) - - t.Run("OpenInfoJSONFile", func(t *testing.T) { - file, err := pfs.Open("/info.json") - if err != nil { - t.Errorf("Failed to open info.json file: %v", err) - } - defer file.Close() - - // Read contents - contents, err := io.ReadAll(file) - if err != nil { - t.Errorf("Failed to read file contents: %v", err) - } - - // Verify the contents contain the inverted map - var infoData map[string]string - err = json.Unmarshal(contents, &infoData) - if err != nil { - t.Errorf("Failed to parse info.json contents: %v", err) - } - - // Check that the key has dashes replaced with underscores - expectedKey := "prestate_test" - if hash, ok := infoData[expectedKey]; !ok || hash != "hash123" { - t.Errorf("Expected info.json to contain mapping from %s to hash123, got %v", expectedKey, hash) - } - }) - - t.Run("OpenNonExistentFile", func(t *testing.T) { - _, err := pfs.Open("/nonexistent.json") - if err == nil { - t.Error("Expected error opening non-existent file") - } - }) - - t.Run("ListDirectory", func(t *testing.T) { - dir, err := pfs.Open("/") - if err != nil { - t.Errorf("Failed to open root directory: %v", err) - } - defer dir.Close() - - files, err := dir.Readdir(-1) - if err != nil { - t.Errorf("Failed to read directory: %v", err) - } - - // We expect both .json and .bin.gz files for hash123, plus info.json - if len(files) != 3 { - t.Errorf("Expected 3 files, got %d", len(files)) - } - - // Verify info.json is included in the directory listing - for _, file := range files { - if file.Name() == "info.json" { - return - } - } - t.Error("info.json not found in directory listing") - }) -} diff --git a/kurtosis-devnet/op-program-svc/interfaces.go b/kurtosis-devnet/op-program-svc/interfaces.go deleted file mode 100644 index 0d93ecb8349ed..0000000000000 --- a/kurtosis-devnet/op-program-svc/interfaces.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "io" - "io/fs" - "os" -) - -// File interface abstracts file operations -type File interface { - fs.File - io.Seeker - Readdir(count int) ([]fs.FileInfo, error) -} - -// FS defines the interface for all filesystem operations -type FS interface { - // Core FS operations - Open(name string) (File, error) - ReadDir(name string) ([]fs.DirEntry, error) - ReadFile(name string) ([]byte, error) - Join(elem ...string) string - Stat(name string) (fs.FileInfo, error) - - // Additional FileSystem operations - MkdirAll(path string, perm os.FileMode) error - Create(name string) (io.WriteCloser, error) -} - -// UploadedFile represents a file that has been uploaded -type UploadedFile interface { - Open() (io.ReadCloser, error) - GetFilename() string -} - -// CommandRunner abstracts command execution for testing -type CommandRunner interface { - Start() error - Wait() error - StdoutPipe() (io.ReadCloser, error) - StderrPipe() (io.ReadCloser, error) - SetDir(dir string) -} - -// CommandFactory creates commands -type CommandFactory interface { - CreateCommand(name string, args ...string) CommandRunner -} diff --git a/kurtosis-devnet/op-program-svc/justfile b/kurtosis-devnet/op-program-svc/justfile deleted file mode 100644 index 3c980fc335feb..0000000000000 --- a/kurtosis-devnet/op-program-svc/justfile +++ /dev/null @@ -1,5 +0,0 @@ -op-program-base TAG='op-program-base:latest': - docker buildx build -f ../../op-program/Dockerfile.repro --target=src -t {{TAG}} ../.. - -op-program-svc TAG='op-program-svc:latest': op-program-base - docker buildx build -f Dockerfile -t {{TAG}} . diff --git a/kurtosis-devnet/op-program-svc/main.go b/kurtosis-devnet/op-program-svc/main.go deleted file mode 100644 index c267d7ec8b598..0000000000000 --- a/kurtosis-devnet/op-program-svc/main.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "net/http" - "path/filepath" -) - -var ( - flagAppRoot = flag.String("app-root", "/app", "Root directory for the application") - flagConfigsDir = flag.String("configs-dir", "chainconfig/configs", "Directory for config files (relative to build-dir)") - flagBuildDir = flag.String("build-dir", "op-program", "Directory where the build command will be executed (relative to app-root)") - flagBuildCmd = flag.String("build-cmd", "just -f repro.justfile build-current", "Build command to execute") - flagPort = flag.Int("port", 8080, "Port to listen on") -) - -func main() { - flag.Parse() - - srv := createServer() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - http.FileServer(srv.proofFS).ServeHTTP(w, r) - case http.MethodPost: - srv.handleUpload(w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } - }) - // Set up routes - http.HandleFunc("/", handler) - - log.Printf("Starting server on :%d with:", srv.port) - log.Printf(" app-root: %s", srv.appRoot) - log.Printf(" configs-dir: %s", filepath.Join(srv.appRoot, srv.buildDir, srv.configsDir)) - log.Printf(" build-dir: %s", filepath.Join(srv.appRoot, srv.buildDir)) - log.Printf(" build-cmd: %s", srv.buildCmd) - log.Printf(" proofs-dir: %s", filepath.Join(srv.appRoot, srv.buildDir, "bin")) - - if err := http.ListenAndServe(fmt.Sprintf(":%d", srv.port), nil); err != nil { - log.Fatalf("Failed to start server: %v", err) - } -} diff --git a/kurtosis-devnet/op-program-svc/mocks.go b/kurtosis-devnet/op-program-svc/mocks.go deleted file mode 100644 index 8fa108cb0c873..0000000000000 --- a/kurtosis-devnet/op-program-svc/mocks.go +++ /dev/null @@ -1,285 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "io" - "io/fs" - "net/http" - "os" - "path/filepath" - "strings" - "time" -) - -// MockFile implements both File and fs.FileInfo interfaces for testing -type MockFile struct { - name string - contents []byte - pos int64 - isDir bool -} - -func NewMockFile(name string, contents []byte) *MockFile { - return &MockFile{ - name: name, - contents: contents, - } -} - -func (m *MockFile) Close() error { return nil } -func (m *MockFile) Read(p []byte) (n int, err error) { - if m.pos >= int64(len(m.contents)) { - return 0, io.EOF - } - n = copy(p, m.contents[m.pos:]) - m.pos += int64(n) - return n, nil -} - -func (m *MockFile) Seek(offset int64, whence int) (int64, error) { - var abs int64 - switch whence { - case io.SeekStart: - abs = offset - case io.SeekCurrent: - abs = m.pos + offset - case io.SeekEnd: - abs = int64(len(m.contents)) + offset - default: - return 0, fmt.Errorf("invalid whence") - } - if abs < 0 { - return 0, fmt.Errorf("negative position") - } - m.pos = abs - return abs, nil -} - -func (m *MockFile) Stat() (fs.FileInfo, error) { return m, nil } -func (m *MockFile) Name() string { return m.name } -func (m *MockFile) Size() int64 { return int64(len(m.contents)) } -func (m *MockFile) Mode() fs.FileMode { return 0644 } -func (m *MockFile) ModTime() time.Time { return time.Now() } -func (m *MockFile) IsDir() bool { return m.isDir } -func (m *MockFile) Sys() interface{} { return nil } -func (m *MockFile) Readdir(count int) ([]fs.FileInfo, error) { - return nil, fmt.Errorf("not implemented") -} - -// MockFS implements FS interface for testing -type MockFS struct { - Files map[string]*MockFile - ShouldFail bool - StatFailPaths map[string]bool // Paths that should fail for Stat - JoinCalls [][]string - MkdirCalls []string - CreateCalls []string -} - -func NewMockFS() *MockFS { - return &MockFS{ - Files: make(map[string]*MockFile), - StatFailPaths: make(map[string]bool), - JoinCalls: make([][]string, 0), - MkdirCalls: make([]string, 0), - CreateCalls: make([]string, 0), - } -} - -// FS interface methods -func (m *MockFS) Open(name string) (File, error) { - if m.ShouldFail { - return nil, fmt.Errorf("mock open error") - } - if file, ok := m.Files[name]; ok { - file.pos = 0 // Reset position for new reads - return file, nil - } - return nil, fs.ErrNotExist -} - -func (m *MockFS) ReadDir(name string) ([]fs.DirEntry, error) { - if m.ShouldFail { - return nil, fmt.Errorf("mock readdir error") - } - var entries []fs.DirEntry - for path, file := range m.Files { - if filepath.Dir(path) == name { - entries = append(entries, fs.FileInfoToDirEntry(file)) - } - } - return entries, nil -} - -func (m *MockFS) ReadFile(name string) ([]byte, error) { - if m.ShouldFail { - return nil, fmt.Errorf("mock readfile error") - } - if file, ok := m.Files[name]; ok { - return file.contents, nil - } - return nil, fs.ErrNotExist -} - -// FileSystem interface methods -func (m *MockFS) MkdirAll(path string, perm os.FileMode) error { - if m.ShouldFail { - return fmt.Errorf("mock mkdir error") - } - m.MkdirCalls = append(m.MkdirCalls, path) - return nil -} - -func (m *MockFS) Create(name string) (io.WriteCloser, error) { - if m.ShouldFail { - return nil, fmt.Errorf("mock create error") - } - m.CreateCalls = append(m.CreateCalls, name) - return &MockWriteCloser{}, nil -} - -func (m *MockFS) Join(elem ...string) string { - m.JoinCalls = append(m.JoinCalls, elem) - return filepath.Join(elem...) -} - -func (m *MockFS) Stat(name string) (fs.FileInfo, error) { - if m.ShouldFail { - return nil, fmt.Errorf("mock stat error") - } - if m.StatFailPaths[name] { - return nil, fmt.Errorf("file not found: %s", name) - } - return m.Files[name], nil -} - -// MockWriteCloser implements io.WriteCloser for testing -type MockWriteCloser struct { - bytes.Buffer -} - -func (m *MockWriteCloser) Close() error { - return nil -} - -// MockUploadedFile implements UploadedFile for testing -type MockUploadedFile struct { - filename string - content []byte -} - -func NewMockUploadedFile(filename string, content []byte) *MockUploadedFile { - return &MockUploadedFile{ - filename: filename, - content: content, - } -} - -func (m *MockUploadedFile) Open() (io.ReadCloser, error) { - return io.NopCloser(bytes.NewReader(m.content)), nil -} - -func (m *MockUploadedFile) GetFilename() string { - return m.filename -} - -// MockCommandRunner implements CommandRunner for testing -type MockCommandRunner struct { - StartCalled bool - WaitCalled bool - ShouldFail bool - Stdout string - Stderr string - Dir string -} - -func (m *MockCommandRunner) Start() error { - if m.ShouldFail { - return fmt.Errorf("mock start error") - } - m.StartCalled = true - return nil -} - -func (m *MockCommandRunner) Wait() error { - if m.ShouldFail { - return fmt.Errorf("mock wait error") - } - m.WaitCalled = true - return nil -} - -func (m *MockCommandRunner) StdoutPipe() (io.ReadCloser, error) { - return io.NopCloser(strings.NewReader(m.Stdout)), nil -} - -func (m *MockCommandRunner) StderrPipe() (io.ReadCloser, error) { - return io.NopCloser(strings.NewReader(m.Stderr)), nil -} - -func (m *MockCommandRunner) SetDir(dir string) { - m.Dir = dir -} - -// MockCommandFactory implements CommandFactory for testing -type MockCommandFactory struct { - Runner *MockCommandRunner -} - -func (f *MockCommandFactory) CreateCommand(name string, args ...string) CommandRunner { - return f.Runner -} - -// MockProofFS is a mock implementation of ProofFS -type MockProofFS struct { - scanProofFilesFn func() error - fs *MockFS -} - -func NewMockProofFS() *MockProofFS { - return &MockProofFS{ - fs: NewMockFS(), - } -} - -func (m *MockProofFS) scanProofFiles() error { - if m.scanProofFilesFn != nil { - return m.scanProofFilesFn() - } - return nil -} - -func (m *MockProofFS) Open(name string) (http.File, error) { - file, err := m.fs.Open(name) - if err != nil { - return nil, err - } - // MockFile implements http.File (including Seek) - return file.(http.File), nil -} - -// AddFile adds a file to the mock filesystem -func (m *MockProofFS) AddFile(name string, contents []byte) { - m.fs.Files[name] = NewMockFile(name, contents) -} - -// MockBuilder is a mock implementation of BuildSystem -type MockBuilder struct { - saveUploadedFilesFn func(files []UploadedFile) error - executeBuildFn func() ([]byte, error) -} - -func (m *MockBuilder) SaveUploadedFiles(files []UploadedFile) error { - if m.saveUploadedFilesFn != nil { - return m.saveUploadedFilesFn(files) - } - return nil -} - -func (m *MockBuilder) ExecuteBuild() ([]byte, error) { - if m.executeBuildFn != nil { - return m.executeBuildFn() - } - return []byte("mock build output"), nil -} diff --git a/kurtosis-devnet/op-program-svc/server.go b/kurtosis-devnet/op-program-svc/server.go deleted file mode 100644 index ad8e16a15b52e..0000000000000 --- a/kurtosis-devnet/op-program-svc/server.go +++ /dev/null @@ -1,158 +0,0 @@ -package main - -import ( - "crypto/sha256" - "encoding/hex" - "fmt" - "io" - "log" - "mime/multipart" - "net/http" - "path/filepath" - "sync" -) - -// ProofFS represents the interface for the proof filesystem -type ProofFS interface { - http.FileSystem - scanProofFiles() error -} - -// BuildSystem represents the interface for the build system -type BuildSystem interface { - SaveUploadedFiles(files []UploadedFile) error - ExecuteBuild() ([]byte, error) -} - -type server struct { - appRoot string - configsDir string - buildDir string - buildCmd string - port int - lastBuildHash string - buildMutex sync.Mutex - proofFS ProofFS - builder BuildSystem -} - -func createServer() *server { - srv := &server{ - appRoot: *flagAppRoot, - configsDir: *flagConfigsDir, - buildDir: *flagBuildDir, - buildCmd: *flagBuildCmd, - port: *flagPort, - } - - // Initialize the proof filesystem - proofsDir := filepath.Join(srv.appRoot, srv.buildDir, "bin") - proofFS := newProofFileSystem(proofsDir) - srv.proofFS = proofFS - - // Initialize the builder - builder := NewBuilder(srv.appRoot, srv.configsDir, srv.buildDir, srv.buildCmd) - srv.builder = builder - - return srv -} - -func (s *server) handleUpload(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - log.Printf("Received upload request from %s", r.RemoteAddr) - - multipartFiles, currentHash, err := s.processMultipartForm(r) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - s.buildMutex.Lock() - defer s.buildMutex.Unlock() - - // Check if we need to rebuild - if currentHash == s.lastBuildHash { - log.Printf("Hash matches last build, skipping") - w.WriteHeader(http.StatusNotModified) - fmt.Fprintf(w, "Files unchanged, skipping build") - return - } - - log.Printf("Hash differs from last build (%s), proceeding with build", s.lastBuildHash) - - // Convert multipart files to UploadedFile interface - files := make([]UploadedFile, len(multipartFiles)) - for i, f := range multipartFiles { - files[i] = NewMultipartUploadedFile(f) - } - - // Save the files using the builder - if err := s.builder.SaveUploadedFiles(files); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Execute the build - out, err := s.builder.ExecuteBuild() - if err != nil { - http.Error(w, fmt.Sprintf("%v\nOutput: %s", err, out), http.StatusInternalServerError) - return - } - - // After successful build, scan for new proof files - if err := s.proofFS.scanProofFiles(); err != nil { - log.Printf("Warning: failed to scan proof files: %v", err) - } - - // Update the last successful build hash - s.lastBuildHash = currentHash - - log.Printf("Build successful, last build hash: %s", currentHash) - w.WriteHeader(http.StatusOK) -} - -func (s *server) processMultipartForm(r *http.Request) ([]*multipart.FileHeader, string, error) { - // Parse the multipart form - if err := r.ParseMultipartForm(1 << 30); err != nil { // 1GB max memory - return nil, "", fmt.Errorf("failed to parse form: %w", err) - } - - // Get uploaded files - files := r.MultipartForm.File["files[]"] - if len(files) == 0 { - return nil, "", fmt.Errorf("no files uploaded") - } - - log.Printf("Processing %d files:", len(files)) - for _, fileHeader := range files { - log.Printf(" - %s (size: %d bytes)", fileHeader.Filename, fileHeader.Size) - } - - // Calculate hash of all files - hash, err := s.calculateFilesHash(files) - if err != nil { - return nil, "", fmt.Errorf("failed to calculate files hash: %w", err) - } - - return files, hash, nil -} - -func (s *server) calculateFilesHash(files []*multipart.FileHeader) (string, error) { - hasher := sha256.New() - for _, fileHeader := range files { - file, err := fileHeader.Open() - if err != nil { - return "", fmt.Errorf("failed to open file: %w", err) - } - if _, err := io.Copy(hasher, file); err != nil { - file.Close() - return "", fmt.Errorf("failed to hash file: %w", err) - } - file.Close() - } - return hex.EncodeToString(hasher.Sum(nil)), nil -} diff --git a/kurtosis-devnet/op-program-svc/server_test.go b/kurtosis-devnet/op-program-svc/server_test.go deleted file mode 100644 index 5831e37cfa096..0000000000000 --- a/kurtosis-devnet/op-program-svc/server_test.go +++ /dev/null @@ -1,251 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "io" - "mime/multipart" - "net/http" - "net/http/httptest" - "strings" - "testing" -) - -func createTestServer(t *testing.T) (*server, *MockProofFS, *MockBuilder) { - t.Helper() - mockProofFS := NewMockProofFS() - mockBuilder := &MockBuilder{} - - srv := &server{ - appRoot: "test-root", - configsDir: "test-configs", - buildDir: "test-build", - buildCmd: "test-cmd", - port: 8080, - proofFS: mockProofFS, - builder: mockBuilder, - } - - return srv, mockProofFS, mockBuilder -} - -func createMultipartRequest(t *testing.T, files map[string][]byte) (*http.Request, error) { - t.Helper() - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - for filename, content := range files { - part, err := writer.CreateFormFile("files[]", filename) - if err != nil { - return nil, err - } - if _, err := io.Copy(part, bytes.NewReader(content)); err != nil { - return nil, err - } - } - - if err := writer.Close(); err != nil { - return nil, err - } - - req := httptest.NewRequest("POST", "/upload", body) - req.Header.Set("Content-Type", writer.FormDataContentType()) - return req, nil -} - -func TestHandleUpload_MethodNotAllowed(t *testing.T) { - srv, _, _ := createTestServer(t) - - req := httptest.NewRequest("GET", "/upload", nil) - w := httptest.NewRecorder() - - srv.handleUpload(w, req) - - if w.Code != http.StatusMethodNotAllowed { - t.Errorf("Expected status code %d, got %d", http.StatusMethodNotAllowed, w.Code) - } -} - -func TestHandleUpload_NoFiles(t *testing.T) { - srv, _, _ := createTestServer(t) - - req := httptest.NewRequest("POST", "/upload", nil) - req.Header.Set("Content-Type", "multipart/form-data; boundary=xxx") - w := httptest.NewRecorder() - - srv.handleUpload(w, req) - - if w.Code != http.StatusBadRequest { - t.Errorf("Expected status code %d, got %d", http.StatusBadRequest, w.Code) - } -} - -func TestHandleUpload_Success(t *testing.T) { - srv, mockProofFS, mockBuilder := createTestServer(t) - - // Setup test data - files := map[string][]byte{ - "test.txt": []byte("test content"), - } - - // Setup mocks - mockBuilder.saveUploadedFilesFn = func(files []UploadedFile) error { - return nil - } - mockBuilder.executeBuildFn = func() ([]byte, error) { - return []byte("build successful"), nil - } - mockProofFS.scanProofFilesFn = func() error { - return nil - } - - // Create request - req, err := createMultipartRequest(t, files) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - - w := httptest.NewRecorder() - srv.handleUpload(w, req) - - // We now expect 200 OK instead of a redirect - if w.Code != http.StatusOK { - t.Errorf("Expected status code %d, got %d", http.StatusOK, w.Code) - } -} - -func TestHandleUpload_SaveError(t *testing.T) { - srv, _, mockBuilder := createTestServer(t) - - // Setup test data - files := map[string][]byte{ - "test.txt": []byte("test content"), - } - - // Setup mock to return error - mockBuilder.saveUploadedFilesFn = func(files []UploadedFile) error { - return fmt.Errorf("failed to save files") - } - - // Create request - req, err := createMultipartRequest(t, files) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - - w := httptest.NewRecorder() - srv.handleUpload(w, req) - - if w.Code != http.StatusInternalServerError { - t.Errorf("Expected status code %d, got %d", http.StatusInternalServerError, w.Code) - } -} - -func TestHandleUpload_BuildError(t *testing.T) { - srv, _, mockBuilder := createTestServer(t) - - // Setup test data - files := map[string][]byte{ - "test.txt": []byte("test content"), - } - - // Setup mocks - mockBuilder.saveUploadedFilesFn = func(files []UploadedFile) error { - return nil - } - mockBuilder.executeBuildFn = func() ([]byte, error) { - return []byte("build failed"), fmt.Errorf("build error") - } - - // Create request - req, err := createMultipartRequest(t, files) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - - w := httptest.NewRecorder() - srv.handleUpload(w, req) - - if w.Code != http.StatusInternalServerError { - t.Errorf("Expected status code %d, got %d", http.StatusInternalServerError, w.Code) - } - - if !strings.Contains(w.Body.String(), "build error") { - t.Errorf("Expected error message to contain 'build error', got %s", w.Body.String()) - } -} - -func TestHandleUpload_ScanError(t *testing.T) { - srv, mockProofFS, mockBuilder := createTestServer(t) - - // Setup test data - files := map[string][]byte{ - "test.txt": []byte("test content"), - } - - // Setup mocks - mockBuilder.saveUploadedFilesFn = func(files []UploadedFile) error { - return nil - } - mockBuilder.executeBuildFn = func() ([]byte, error) { - return []byte("build successful"), nil - } - mockProofFS.scanProofFilesFn = func() error { - return fmt.Errorf("scan error") - } - - // Create request - req, err := createMultipartRequest(t, files) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - - w := httptest.NewRecorder() - srv.handleUpload(w, req) - - // Even with scan error, we should still return 200 OK - if w.Code != http.StatusOK { - t.Errorf("Expected status code %d, got %d", http.StatusOK, w.Code) - } -} - -func TestHandleUpload_UnchangedFiles(t *testing.T) { - srv, _, _ := createTestServer(t) - - // Setup test data - files := map[string][]byte{ - "test.txt": []byte("test content"), - } - - // First request - req1, err := createMultipartRequest(t, files) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - - w1 := httptest.NewRecorder() - srv.handleUpload(w1, req1) - - // First request should return 200 OK - if w1.Code != http.StatusOK { - t.Errorf("Expected status code %d, got %d", http.StatusOK, w1.Code) - } - - // Second request with same files - req2, err := createMultipartRequest(t, files) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - - w2 := httptest.NewRecorder() - srv.handleUpload(w2, req2) - - // Second request with unchanged files should return 304 Not Modified - if w2.Code != http.StatusNotModified { - t.Errorf("Expected status code %d, got %d", http.StatusNotModified, w2.Code) - } - - if !strings.Contains(w2.Body.String(), "Files unchanged") { - t.Errorf("Expected response to contain 'Files unchanged', got %s", w2.Body.String()) - } -} diff --git a/kurtosis-devnet/optimism-package-trampoline/README.md b/kurtosis-devnet/optimism-package-trampoline/README.md deleted file mode 100644 index 41853ea712f2b..0000000000000 --- a/kurtosis-devnet/optimism-package-trampoline/README.md +++ /dev/null @@ -1,7 +0,0 @@ -This package contains a mostly empty Kurtosis package, -that trampolines to github.com/ethpandas/optimism-package. - -The goal here is to use the `replace` section of `kurtosis.yml` -as a "lockfile" for our package dependencies. - -This way, we achieve reproducibility for our devnet deployments. \ No newline at end of file diff --git a/kurtosis-devnet/optimism-package-trampoline/kurtosis.yml b/kurtosis-devnet/optimism-package-trampoline/kurtosis.yml deleted file mode 100644 index 06e9db8afd93d..0000000000000 --- a/kurtosis-devnet/optimism-package-trampoline/kurtosis.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: github.com/ethereum-optimism/optimism/kurtosis-devnet/optimism-package-trampoline -description: |- - A trampoline package for optimism-package. This one is reproducible, due to the replace directives below. -replace: - github.com/ethpandaops/optimism-package: github.com/ethpandaops/optimism-package@89e0b8cacab9f7e9f74d53b72d4870092825d577 - github.com/ethpandaops/ethereum-package: github.com/ethpandaops/ethereum-package@83830d44823767af65eda7dfe6b26c87c536c4cf - github.com/kurtosis-tech/prometheus-package: github.com/kurtosis-tech/prometheus-package@637c9dea933be18e47f96cadc0d9bb0e3a5aa9d6 # v1.0.0 - github.com/kurtosis-tech/postgres-package: github.com/kurtosis-tech/postgres-package@9cbdde2c55e8d1656deb87821465a2ad244d8b33 # v1.0.0 diff --git a/kurtosis-devnet/optimism-package-trampoline/main.star b/kurtosis-devnet/optimism-package-trampoline/main.star deleted file mode 100644 index d1b2d34b0331f..0000000000000 --- a/kurtosis-devnet/optimism-package-trampoline/main.star +++ /dev/null @@ -1,5 +0,0 @@ -optimism_package = import_module("github.com/ethpandaops/optimism-package/main.star") - -def run(plan, args): - # just delegate to optimism-package - optimism_package.run(plan, args) diff --git a/kurtosis-devnet/pkg/build/contracts.go b/kurtosis-devnet/pkg/build/contracts.go deleted file mode 100644 index b21f523c5f9a0..0000000000000 --- a/kurtosis-devnet/pkg/build/contracts.go +++ /dev/null @@ -1,401 +0,0 @@ -package build - -import ( - "bytes" - "context" - "crypto/sha256" - "encoding/hex" - "fmt" - "io" - "log" - "os" - "path/filepath" - "strings" - "text/template" - "time" - - ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/enclave" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/util" - "github.com/spf13/afero" - "go.opentelemetry.io/otel" -) - -// ContractBuilder handles building smart contracts using just commands -type ContractBuilder struct { - // Base directory where the build commands should be executed - baseDir string - // Template for the build command - cmdTemplate *template.Template - - // Dry run mode - dryRun bool - - builtContracts map[string]string - - enclave string - - // Command factory for testing - cmdFactory cmdFactory - // Enclave manager for testing - enclaveManager *enclave.KurtosisEnclaveManager - // Enclave filesystem for testing - enclaveFS *ktfs.EnclaveFS - // Filesystem for operations - fs afero.Fs -} - -const ( - contractsCmdTemplateStr = "just {{ .ContractsPath }}/build-no-tests" - relativeContractsPath = "../packages/contracts-bedrock" - solidityCachePath = "cache/solidity-files-cache.json" -) - -var defaultContractTemplate *template.Template - -func init() { - defaultContractTemplate = template.Must(template.New("contract_build_cmd").Parse(contractsCmdTemplateStr)) -} - -type ContractBuilderOptions func(*ContractBuilder) - -func WithContractBaseDir(baseDir string) ContractBuilderOptions { - return func(b *ContractBuilder) { - b.baseDir = baseDir - } -} - -func WithContractDryRun(dryRun bool) ContractBuilderOptions { - return func(b *ContractBuilder) { - b.dryRun = dryRun - } -} - -func WithContractEnclave(enclave string) ContractBuilderOptions { - return func(b *ContractBuilder) { - b.enclave = enclave - } -} - -func WithContractEnclaveManager(manager *enclave.KurtosisEnclaveManager) ContractBuilderOptions { - return func(b *ContractBuilder) { - b.enclaveManager = manager - } -} - -func WithContractFS(fs afero.Fs) ContractBuilderOptions { - return func(b *ContractBuilder) { - b.fs = fs - } -} - -// NewContractBuilder creates a new ContractBuilder instance -func NewContractBuilder(opts ...ContractBuilderOptions) *ContractBuilder { - b := &ContractBuilder{ - baseDir: ".", - cmdTemplate: defaultContractTemplate, - dryRun: false, - builtContracts: make(map[string]string), - cmdFactory: defaultCmdFactory, - enclaveManager: nil, - enclaveFS: nil, - fs: afero.NewOsFs(), // Default to OS filesystem - } - - for _, opt := range opts { - opt(b) - } - - return b -} - -// Build executes the contract build command -func (b *ContractBuilder) Build(ctx context.Context, _ string) (string, error) { - _, span := otel.Tracer("contract-builder").Start(ctx, "build contracts") - defer span.End() - - // since we ignore layer for now, we can skip the build if the file already - // exists: it'll be the same file! - if url, ok := b.builtContracts[""]; ok { - return url, nil - } - - log.Println("Building contracts bundle") - - // Execute template to get command string - var cmdBuf bytes.Buffer - if err := b.cmdTemplate.Execute(&cmdBuf, map[string]string{ - "ContractsPath": relativeContractsPath, - }); err != nil { - return "", fmt.Errorf("failed to execute command template: %w", err) - } - - // Create command using the factory - cmd := b.cmdFactory("sh", "-c", cmdBuf.String()) - cmd.SetDir(b.baseDir) - - if b.dryRun { - return "artifact://contracts", nil - } - - output, err := cmd.CombinedOutput() - if err != nil { - return "", fmt.Errorf("contract build command failed: %w\nOutput: %s", err, string(output)) - } - - name, err := b.createContractsArtifact() - if err != nil { - return "", fmt.Errorf("failed to create contracts artifact: %w", err) - } - - url := fmt.Sprintf("artifact://%s", name) - b.builtContracts[""] = url - return url, nil -} - -func (b *ContractBuilder) GetContractUrl() string { - if b.dryRun { - return "artifact://contracts" - } - return fmt.Sprintf("artifact://%s", b.getBuiltContractName()) -} - -func (b *ContractBuilder) getBuiltContractName() string { - return fmt.Sprintf("contracts-%s", b.buildHash()) -} - -func (b *ContractBuilder) buildHash() string { - // the solidity cache file contains up-to-date information about the current - // state of the build, so it's suitable to provide a unique hash. - cacheFilePath := filepath.Join(b.baseDir, relativeContractsPath, solidityCachePath) - - fileData, err := afero.ReadFile(b.fs, cacheFilePath) - if err != nil { - log.Printf("Error reading solidity cache file: %v", err) - return "error" - } - - hash := sha256.Sum256(fileData) - return hex.EncodeToString(hash[:]) -} - -func (b *ContractBuilder) createContractsArtifact() (name string, retErr error) { - // Create context with 10-minute timeout for artifact upload operations - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) - defer cancel() - - name = b.getBuiltContractName() - - // Ensure the enclave exists - var err error - if b.enclaveManager == nil { - return "", fmt.Errorf("enclave manager not set") - } - - // TODO: this is not ideal, we should feed the resulting enclave into the - // EnclaveFS constructor. As it is, we're making sure the enclave exists, - // and we recreate an enclave context for NewEnclaveFS.. - _, err = b.enclaveManager.GetEnclave(ctx, b.enclave) - if err != nil { - return "", fmt.Errorf("failed to get or create enclave: %w", err) - } - - // Create a new Kurtosis filesystem for the specified enclave - var enclaveFS *ktfs.EnclaveFS - if b.enclaveFS != nil { - enclaveFS = b.enclaveFS - } else { - enclaveFS, err = ktfs.NewEnclaveFS(ctx, b.enclave) - if err != nil { - return "", fmt.Errorf("failed to create enclave filesystem: %w", err) - } - } - - // Check if artifact already exists with retry logic - artifactNames, getAllErr := util.WithRetry(ctx, "GetAllArtifactNames", func() ([]string, error) { - return enclaveFS.GetAllArtifactNames(ctx) - }) - - if getAllErr != nil { - log.Printf("Warning: Failed to retrieve artifact names: %v", getAllErr) - } else { - for _, existingName := range artifactNames { - if existingName == name { - log.Printf("Artifact '%s' already exists, skipping creation", name) - return name, nil - } - } - } - - // Check the base contracts directory - contractsDir := filepath.Join(b.baseDir, relativeContractsPath) - exists, err := afero.DirExists(b.fs, contractsDir) - if err != nil || !exists { - return "", fmt.Errorf("contracts directory not found at %s: %w", contractsDir, err) - } - - // Create temp directory to hold the files we want to include - tempDir, err := afero.TempDir(b.fs, "", "contracts-artifacts-*") - if err != nil { - return "", fmt.Errorf("failed to create temp directory: %w", err) - } - defer func() { - if err := b.fs.RemoveAll(tempDir); err != nil && retErr == nil { - retErr = fmt.Errorf("failed to cleanup temporary directory: %w", err) - } - }() - - // Populate the temp directory with the necessary files - if err := b.populateContractsArtifact(contractsDir, tempDir); err != nil { - return "", fmt.Errorf("failed to populate contracts artifact: %w", err) - } - - // Create file readers for the artifact - readers, err := b.createArtifactReaders(tempDir) - if err != nil { - return "", fmt.Errorf("failed to create artifact readers: %w", err) - } - - // Upload the artifact with retry logic - _, err = util.WithRetry(ctx, fmt.Sprintf("PutArtifact(%s)", name), func() (struct{}, error) { - return struct{}{}, enclaveFS.PutArtifact(ctx, name, readers...) - }) - - if err != nil { - return "", fmt.Errorf("failed to upload artifact: %w", err) - } - - return -} - -// createArtifactReaders creates file readers for all files in the directory -func (b *ContractBuilder) createArtifactReaders(dir string) ([]*ktfs.ArtifactFileReader, error) { - var readers []*ktfs.ArtifactFileReader - var openFiles []io.Closer - - err := afero.Walk(b.fs, dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - // Skip directories themselves - if info.IsDir() { - return nil - } - - // Get relative path from base dir - relPath, err := filepath.Rel(dir, path) - if err != nil { - return fmt.Errorf("failed to get relative path: %w", err) - } - - // Open the file - file, err := b.fs.Open(path) - if err != nil { - return fmt.Errorf("failed to open file %s: %w", path, err) - } - openFiles = append(openFiles, file) - - // Add file reader to the list - readers = append(readers, ktfs.NewArtifactFileReader(relPath, file)) - - return nil - }) - - if err != nil { - // Close any open files - for _, file := range openFiles { - file.Close() - } - return nil, fmt.Errorf("failed to prepare artifact files: %w", err) - } - - return readers, nil -} - -// populateContractsArtifact populates the temporary directory with required contract files -func (b *ContractBuilder) populateContractsArtifact(contractsDir, tempDir string) error { - // Handle forge-artifacts directories - exclude *.t.sol directories as we don't need tests. - // op-deployer will need contracts and scripts. - forgeArtifactsPath := filepath.Join(contractsDir, "forge-artifacts") - exists, err := afero.DirExists(b.fs, forgeArtifactsPath) - if err != nil { - return fmt.Errorf("failed to check forge-artifacts directory: %w", err) - } - if !exists { - return nil - } - - entries, err := afero.ReadDir(b.fs, forgeArtifactsPath) - if err != nil { - return fmt.Errorf("failed to read forge-artifacts directory: %w", err) - } - - for _, entry := range entries { - if !entry.IsDir() { - continue - } - - // Skip test directories - if strings.HasSuffix(entry.Name(), ".t.sol") { - continue - } - - srcPath := filepath.Join(forgeArtifactsPath, entry.Name()) - destPath := filepath.Join(tempDir, entry.Name()) - - // Create destination directory - if err := b.fs.MkdirAll(destPath, 0755); err != nil { - return fmt.Errorf("failed to create directory %s: %w", destPath, err) - } - - // Copy directory contents - err = afero.Walk(b.fs, srcPath, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - // Get path relative to source directory - relPath, err := filepath.Rel(srcPath, path) - if err != nil { - return fmt.Errorf("failed to get relative path: %w", err) - } - - // Skip root directory - if relPath == "." { - return nil - } - - destPath := filepath.Join(destPath, relPath) - - if info.IsDir() { - return b.fs.MkdirAll(destPath, info.Mode()) - } - - // Copy file contents - srcFile, err := b.fs.Open(path) - if err != nil { - return fmt.Errorf("failed to open source file: %w", err) - } - defer srcFile.Close() - - destFile, err := b.fs.Create(destPath) - if err != nil { - return fmt.Errorf("failed to create destination file: %w", err) - } - defer destFile.Close() - - if _, err := io.Copy(destFile, srcFile); err != nil { - return fmt.Errorf("failed to copy file contents: %w", err) - } - - return b.fs.Chmod(destPath, info.Mode()) - }) - - if err != nil { - return fmt.Errorf("failed to copy directory %s: %w", srcPath, err) - } - } - - return nil -} diff --git a/kurtosis-devnet/pkg/build/contracts_test.go b/kurtosis-devnet/pkg/build/contracts_test.go deleted file mode 100644 index a4cf312297c8c..0000000000000 --- a/kurtosis-devnet/pkg/build/contracts_test.go +++ /dev/null @@ -1,387 +0,0 @@ -package build - -import ( - "bytes" - "context" - "fmt" - "io" - "path/filepath" - "testing" - - ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/enclave" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/enclaves" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/starlark_run_config" - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type testCmd struct { - output []byte - err error - dir string - stdout *bytes.Buffer - stderr *bytes.Buffer -} - -func (c *testCmd) CombinedOutput() ([]byte, error) { - return c.output, c.err -} - -func (c *testCmd) Dir() string { - return c.dir -} - -func (c *testCmd) SetDir(dir string) { - c.dir = dir -} - -func (c *testCmd) Run() error { - return c.err -} - -func (c *testCmd) SetOutput(stdout, stderr *bytes.Buffer) { - c.stdout = stdout - c.stderr = stderr -} - -func testCmdFactory(output []byte, err error) cmdFactory { - return func(name string, arg ...string) cmdRunner { - return &testCmd{output: output, err: err} - } -} - -type testEnclaveFS struct { - fs afero.Fs - artifact string -} - -func (e *testEnclaveFS) GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) { - if e.artifact == "" { - return []*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid{}, nil - } - return []*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid{ - { - FileName: e.artifact, - FileUuid: "test-uuid", - }, - }, nil -} - -func (e *testEnclaveFS) DownloadFilesArtifact(ctx context.Context, name string) ([]byte, error) { - return []byte("test content"), nil -} - -func (e *testEnclaveFS) UploadFiles(pathToUpload string, artifactName string) (services.FilesArtifactUUID, services.FileArtifactName, error) { - e.artifact = artifactName - return "test-uuid", services.FileArtifactName(artifactName), nil -} - -func (m *testEnclaveFS) GetAllArtifactNames(ctx context.Context) ([]string, error) { - if m.artifact == "" { - return []string{}, nil - } - return []string{m.artifact}, nil -} - -func (m *testEnclaveFS) PutArtifact(ctx context.Context, artifactName string, content []byte) error { - reader := bytes.NewReader(content) - file, err := m.fs.Create(artifactName) - if err != nil { - return err - } - defer file.Close() - _, err = io.Copy(file, reader) - if err != nil { - return err - } - m.artifact = artifactName - return nil -} - -func (m *testEnclaveFS) GetArtifact(ctx context.Context, name string) (*ktfs.Artifact, error) { - return nil, fmt.Errorf("not implemented") -} - -type testEnclaveContext struct{} - -func (e *testEnclaveContext) RunStarlarkPackage(ctx context.Context, pkg string, config *starlark_run_config.StarlarkRunConfig) (<-chan interfaces.StarlarkResponse, string, error) { - return nil, "", nil -} - -func (e *testEnclaveContext) RunStarlarkScript(ctx context.Context, script string, config *starlark_run_config.StarlarkRunConfig) error { - return nil -} - -func (e *testEnclaveContext) GetEnclaveUuid() enclaves.EnclaveUUID { - return enclaves.EnclaveUUID("test-enclave-uuid") -} - -func (e *testEnclaveContext) GetServices() (map[services.ServiceName]services.ServiceUUID, error) { - return nil, nil -} - -func (e *testEnclaveContext) GetService(serviceIdentifier string) (interfaces.ServiceContext, error) { - return nil, nil -} - -func (e *testEnclaveContext) GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) { - return nil, nil -} - -type testKurtosisContext struct{} - -func (c *testKurtosisContext) CreateEnclave(ctx context.Context, name string) (interfaces.EnclaveContext, error) { - return &testEnclaveContext{}, nil -} - -func (c *testKurtosisContext) GetEnclave(ctx context.Context, name string) (interfaces.EnclaveContext, error) { - return &testEnclaveContext{}, nil -} - -func (c *testKurtosisContext) GetEnclaveStatus(ctx context.Context, name string) (interfaces.EnclaveStatus, error) { - return interfaces.EnclaveStatusRunning, nil -} - -func (c *testKurtosisContext) Clean(ctx context.Context, destroyAll bool) ([]interfaces.EnclaveNameAndUuid, error) { - return []interfaces.EnclaveNameAndUuid{}, nil -} - -func (c *testKurtosisContext) DestroyEnclave(ctx context.Context, name string) error { - return nil -} - -func setupTestFS(t *testing.T) (*testEnclaveFS, afero.Fs) { - fs := afero.NewMemMapFs() - - // Create the contracts directory structure - contractsDir := filepath.Join(".", relativeContractsPath) - require.NoError(t, fs.MkdirAll(contractsDir, 0755)) - - // Create a mock solidity cache file - cacheDir := filepath.Join(contractsDir, "cache") - require.NoError(t, fs.MkdirAll(cacheDir, 0755)) - require.NoError(t, afero.WriteFile(fs, filepath.Join(cacheDir, "solidity-files-cache.json"), []byte("test cache"), 0644)) - - // Create forge-artifacts directory with test files - forgeDir := filepath.Join(contractsDir, "forge-artifacts") - require.NoError(t, fs.MkdirAll(forgeDir, 0755)) - - // Create some test contract artifacts - contractDirs := []string{"Contract1.sol", "Contract2.sol"} - for _, dir := range contractDirs { - artifactDir := filepath.Join(forgeDir, dir) - require.NoError(t, fs.MkdirAll(artifactDir, 0755)) - require.NoError(t, afero.WriteFile(fs, filepath.Join(artifactDir, "artifact.json"), []byte("test artifact"), 0644)) - } - - // Create a test contract directory - testContractDir := filepath.Join(forgeDir, "TestContract.t.sol") - require.NoError(t, fs.MkdirAll(testContractDir, 0755)) - require.NoError(t, afero.WriteFile(fs, filepath.Join(testContractDir, "artifact.json"), []byte("test artifact"), 0644)) - - return &testEnclaveFS{fs: fs}, fs -} - -func TestContractBuilder_Build(t *testing.T) { - tests := []struct { - name string - setupCmd func() *testCmd - expectError bool - expectedOutput string - }{ - { - name: "successful build", - setupCmd: func() *testCmd { - return &testCmd{ - output: []byte("build successful"), - err: nil, - dir: ".", - } - }, - expectError: false, - expectedOutput: "artifact://contracts-ce0456a3c5a930d170e08492989cf52b416562106c8040bc384548bfe142eaa2", // hash of "test cache" - }, - { - name: "build command fails", - setupCmd: func() *testCmd { - return &testCmd{ - output: []byte("build failed"), - err: fmt.Errorf("command failed"), - } - }, - expectError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Setup test filesystem - testFS, memFS := setupTestFS(t) - - // Create mock command - mockCmd := tt.setupCmd() - - // Create mock enclave manager - enclaveManager, err := enclave.NewKurtosisEnclaveManager( - enclave.WithKurtosisContext(&testKurtosisContext{}), - ) - require.NoError(t, err) - - // Create contract builder with mocks - builder := NewContractBuilder( - WithContractFS(memFS), - WithContractBaseDir("."), - WithContractDryRun(false), - ) - builder.cmdFactory = testCmdFactory(mockCmd.output, mockCmd.err) - builder.enclaveFS, err = ktfs.NewEnclaveFS(context.Background(), "test-enclave", ktfs.WithEnclaveCtx(testFS), ktfs.WithFs(memFS)) - require.NoError(t, err) - builder.enclaveManager = enclaveManager - - // Execute build - output, err := builder.Build(context.Background(), "") - - // Verify results - if tt.expectError { - assert.Error(t, err) - return - } - - require.NoError(t, err) - assert.Equal(t, tt.expectedOutput, output) - assert.Equal(t, mockCmd.dir, ".") - }) - } -} - -func TestContractBuilder_createContractsArtifact(t *testing.T) { - testFS, memFS := setupTestFS(t) - - // Create mock enclave manager - enclaveManager, err := enclave.NewKurtosisEnclaveManager( - enclave.WithKurtosisContext(&testKurtosisContext{}), - ) - require.NoError(t, err) - - builder := NewContractBuilder( - WithContractFS(memFS), - WithContractBaseDir("."), - ) - builder.enclaveFS, err = ktfs.NewEnclaveFS(context.Background(), "test-enclave", ktfs.WithEnclaveCtx(testFS), ktfs.WithFs(memFS)) - require.NoError(t, err) - builder.enclaveManager = enclaveManager - - // Create the artifact - name, err := builder.createContractsArtifact() - require.NoError(t, err) - - // Verify the artifact was created - artifacts, err := builder.enclaveFS.GetAllArtifactNames(context.Background()) - require.NoError(t, err) - assert.Contains(t, artifacts, name) - - // Verify it skips test contracts - for _, artifact := range artifacts { - assert.NotContains(t, artifact, "TestContract.t.sol") - } -} - -func TestContractBuilder_buildHash(t *testing.T) { - _, memFS := setupTestFS(t) - - builder := NewContractBuilder( - WithContractFS(memFS), - WithContractBaseDir("."), - ) - - // Get the hash - hash := builder.buildHash() - - // Verify it's not empty or "error" - assert.NotEmpty(t, hash) - assert.NotEqual(t, "error", hash) - - // Verify it's consistent - hash2 := builder.buildHash() - assert.Equal(t, hash, hash2) - - // Modify the cache file and verify the hash changes - cacheFile := filepath.Join(".", relativeContractsPath, solidityCachePath) - err := afero.WriteFile(memFS, cacheFile, []byte("modified cache"), 0644) - require.NoError(t, err) - - hash3 := builder.buildHash() - assert.NotEqual(t, hash, hash3) -} - -func TestContractBuilder_populateContractsArtifact(t *testing.T) { - _, memFS := setupTestFS(t) - - builder := NewContractBuilder( - WithContractFS(memFS), - WithContractBaseDir("."), - ) - - // Create a temporary directory for the test - tempDir, err := afero.TempDir(memFS, "", "test-artifacts-*") - require.NoError(t, err) - defer func() { - _ = memFS.RemoveAll(tempDir) - }() - - // Populate the artifacts - contractsDir := filepath.Join(".", relativeContractsPath) - err = builder.populateContractsArtifact(contractsDir, tempDir) - require.NoError(t, err) - - // Verify the directory structure - exists, err := afero.DirExists(memFS, filepath.Join(tempDir, "Contract1.sol")) - assert.NoError(t, err) - assert.True(t, exists) - - exists, err = afero.DirExists(memFS, filepath.Join(tempDir, "Contract2.sol")) - assert.NoError(t, err) - assert.True(t, exists) - - // Verify test contracts are not copied - exists, err = afero.DirExists(memFS, filepath.Join(tempDir, "TestContract.t.sol")) - assert.NoError(t, err) - assert.False(t, exists) - - // Verify file contents - content, err := afero.ReadFile(memFS, filepath.Join(tempDir, "Contract1.sol", "artifact.json")) - require.NoError(t, err) - assert.Equal(t, "test artifact", string(content)) -} - -func TestContractBuilder_GetContractUrl(t *testing.T) { - _, memFS := setupTestFS(t) - - builder := NewContractBuilder( - WithContractFS(memFS), - WithContractBaseDir("."), - ) - - // Get the contract URL - url := builder.GetContractUrl() - - // Verify the format is correct - assert.Regexp(t, `^artifact://contracts-[a-f0-9]{64}$`, url) - - // Verify it's consistent - url2 := builder.GetContractUrl() - assert.Equal(t, url, url2) - - // Verify it changes when the cache file changes - cacheFile := filepath.Join(".", relativeContractsPath, solidityCachePath) - err := afero.WriteFile(memFS, cacheFile, []byte("modified cache"), 0644) - require.NoError(t, err) - - url3 := builder.GetContractUrl() - assert.NotEqual(t, url, url3) -} diff --git a/kurtosis-devnet/pkg/build/docker.go b/kurtosis-devnet/pkg/build/docker.go deleted file mode 100644 index c9142c574481c..0000000000000 --- a/kurtosis-devnet/pkg/build/docker.go +++ /dev/null @@ -1,343 +0,0 @@ -package build - -import ( - "bytes" - "context" - "fmt" - "log" - "net/url" - "os" - "os/exec" - "runtime" - "strings" - "sync" - "text/template" - "time" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/client" - "go.opentelemetry.io/otel" - "golang.org/x/sync/semaphore" -) - -// cmdRunner abstracts command execution for testing -type cmdRunner interface { - // CombinedOutput is kept for potential future use or simpler scenarios - CombinedOutput() ([]byte, error) - // Run starts the command and waits for it to complete. - // It's often preferred when you want to manage stdout/stderr separately. - Run() error - // SetOutput sets the writers for stdout and stderr. - SetOutput(stdout, stderr *bytes.Buffer) - Dir() string - SetDir(dir string) -} - -// defaultCmdRunner is the default implementation that uses exec.Command -type defaultCmdRunner struct { - *exec.Cmd - stdout *bytes.Buffer - stderr *bytes.Buffer -} - -func (r *defaultCmdRunner) CombinedOutput() ([]byte, error) { - if r.stdout == nil || r.stderr == nil { - var combined bytes.Buffer - r.Cmd.Stdout = &combined - r.Cmd.Stderr = &combined - err := r.Cmd.Run() - return combined.Bytes(), err - } - err := r.Run() - combined := append(r.stdout.Bytes(), r.stderr.Bytes()...) - return combined, err -} - -func (r *defaultCmdRunner) SetOutput(stdout, stderr *bytes.Buffer) { - r.stdout = stdout - r.stderr = stderr - r.Cmd.Stdout = stdout - r.Cmd.Stderr = stderr -} - -func (r *defaultCmdRunner) Run() error { - return r.Cmd.Run() -} - -func (r *defaultCmdRunner) Dir() string { - return r.Cmd.Dir -} - -func (r *defaultCmdRunner) SetDir(dir string) { - r.Cmd.Dir = dir -} - -// cmdFactory creates commands -type cmdFactory func(name string, arg ...string) cmdRunner - -// defaultCmdFactory is the default implementation that uses exec.Command -func defaultCmdFactory(name string, arg ...string) cmdRunner { - return &defaultCmdRunner{Cmd: exec.Command(name, arg...)} -} - -// dockerClient interface defines the Docker client methods we use -type dockerClient interface { - ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) - ImageTag(ctx context.Context, source, target string) error -} - -// dockerProvider abstracts the creation of Docker clients -type dockerProvider interface { - newClient() (dockerClient, error) -} - -// defaultDockerProvider is the default implementation of dockerProvider -type defaultDockerProvider struct{} - -func (p *defaultDockerProvider) newClient() (dockerClient, error) { - opts := []client.Opt{client.FromEnv} - - // Check if default docker socket exists - hostURL, err := url.Parse(client.DefaultDockerHost) - if err != nil { - return nil, fmt.Errorf("failed to parse default docker host: %w", err) - } - - // For unix sockets, check if the socket file exists - unixOS := runtime.GOOS == "linux" || runtime.GOOS == "darwin" - if hostURL.Scheme == "unix" && unixOS { - if _, err := os.Stat(hostURL.Path); os.IsNotExist(err) { - // Default socket doesn't exist, try to find an alternate location. Docker Desktop uses a socket in the home directory. - homeDir, err := os.UserHomeDir() - if err != nil { - return nil, fmt.Errorf("failed to get user home directory: %w", err) - } - // Try to use the non-privileged socket if available - homeSocketPath := fmt.Sprintf("%s/.docker/run/docker.sock", homeDir) - if runtime.GOOS == "linux" { - homeSocketPath = fmt.Sprintf("%s/.docker/desktop/docker.sock", homeDir) - } - - // If that socket exists, make it the default. Otherwise, leave it alone, and hope some environment variable has been set. - if _, err := os.Stat(homeSocketPath); err == nil { - socketURL := &url.URL{ - Scheme: "unix", - Path: homeSocketPath, - } - // prepend the host, so that it can still be overridden by the environment. - opts = append([]client.Opt{client.WithHost(socketURL.String())}, opts...) - } - } - } - - return client.NewClientWithOpts(opts...) -} - -// DockerBuilder handles building docker images using just commands -type DockerBuilder struct { - // Base directory where the build commands should be executed - baseDir string - // Template for the build command - cmdTemplate *template.Template - // Dry run mode - dryRun bool - // Docker provider for creating clients - dockerProvider dockerProvider - // Command factory for testing - cmdFactory cmdFactory - // Concurrency limiting semaphore - sem *semaphore.Weighted - // Mutex to protect shared state (buildStates) - mu sync.Mutex - // Tracks the state of builds (ongoing or completed) - buildStates map[string]*buildState -} - -// buildState stores the result and status of a build -type buildState struct { - result string - err error - done chan struct{} - once sync.Once -} - -const cmdTemplateStr = "just {{.ProjectName}}-image {{.ImageTag}}" - -var defaultCmdTemplate *template.Template - -func init() { - defaultCmdTemplate = template.Must(template.New("docker_build_cmd").Parse(cmdTemplateStr)) -} - -type DockerBuilderOptions func(*DockerBuilder) - -func WithDockerCmdTemplate(cmdTemplate *template.Template) DockerBuilderOptions { - return func(b *DockerBuilder) { - b.cmdTemplate = cmdTemplate - } -} - -func WithDockerBaseDir(baseDir string) DockerBuilderOptions { - return func(b *DockerBuilder) { - b.baseDir = baseDir - } -} - -func WithDockerDryRun(dryRun bool) DockerBuilderOptions { - return func(b *DockerBuilder) { - b.dryRun = dryRun - } -} - -// WithDockerConcurrency sets the maximum number of concurrent builds. -func WithDockerConcurrency(limit int) DockerBuilderOptions { - if limit <= 0 { - limit = 1 - } - if limit >= 32 { - limit = 32 - } - return func(b *DockerBuilder) { - b.sem = semaphore.NewWeighted(int64(limit)) - } -} - -// NewDockerBuilder creates a new DockerBuilder instance -func NewDockerBuilder(opts ...DockerBuilderOptions) *DockerBuilder { - b := &DockerBuilder{ - baseDir: ".", - cmdTemplate: defaultCmdTemplate, - dryRun: false, - dockerProvider: &defaultDockerProvider{}, - cmdFactory: defaultCmdFactory, - sem: semaphore.NewWeighted(1), - buildStates: make(map[string]*buildState), - } - - for _, opt := range opts { - opt(b) - } - - return b -} - -// templateData holds the data for the command template -type templateData struct { - ImageTag string - ProjectName string -} - -// Build ensures the docker image for the given project is built, respecting concurrency limits. -// It blocks until the specific requested build is complete. Other builds may run concurrently. -func (b *DockerBuilder) Build(ctx context.Context, projectName, imageTag string) (string, error) { - b.mu.Lock() - state, exists := b.buildStates[projectName] - if !exists { - state = &buildState{ - done: make(chan struct{}), - } - b.buildStates[projectName] = state - } - b.mu.Unlock() - - if !exists { - state.once.Do(func() { - err := b.executeBuild(ctx, projectName, imageTag, state) - if err != nil { - state.err = err - state.result = "" - } - close(state.done) - }) - } else { - <-state.done - } - - return state.result, state.err -} - -func (b *DockerBuilder) executeBuild(ctx context.Context, projectName, initialImageTag string, state *buildState) error { - ctx, span := otel.Tracer("docker-builder").Start(ctx, fmt.Sprintf("build %s", projectName)) - defer span.End() - - log.Printf("Build started for project: %s (tag: %s)", projectName, initialImageTag) - - if b.dryRun { - log.Printf("Dry run: Skipping build for project %s", projectName) - state.result = initialImageTag - return nil - } - - if err := b.sem.Acquire(ctx, 1); err != nil { - log.Printf("Failed to acquire build semaphore for %s: %v", projectName, err) - return fmt.Errorf("failed to acquire semaphore: %w", err) - } - defer b.sem.Release(1) - - data := templateData{ - ImageTag: initialImageTag, - ProjectName: projectName, - } - - var cmdBuf bytes.Buffer - if err := b.cmdTemplate.Execute(&cmdBuf, data); err != nil { - log.Printf("Build failed for %s: Failed to execute command template: %v", projectName, err) - return fmt.Errorf("failed to execute command template: %w", err) - } - cmdStr := cmdBuf.String() - - cmd := b.cmdFactory("sh", "-c", cmdStr) - var stdoutBuf, stderrBuf bytes.Buffer - cmd.SetOutput(&stdoutBuf, &stderrBuf) - - startTime := time.Now() - log.Printf("Executing build command for %s: %s", projectName, cmdStr) - err := cmd.Run() - duration := time.Since(startTime) - - if err != nil { - log.Printf("Build failed for %s after %s: %v", projectName, duration, err) - log.Printf("--- Start Output (stdout) for failed %s ---", projectName) - log.Print(stdoutBuf.String()) - log.Printf("--- End Output (stdout) for failed %s ---", projectName) - log.Printf("--- Start Output (stderr) for failed %s ---", projectName) - log.Print(stderrBuf.String()) - log.Printf("--- End Output (stderr) for failed %s ---", projectName) - return fmt.Errorf("build command failed: %w", err) - } - - dockerClient, err := b.dockerProvider.newClient() - if err != nil { - log.Printf("Build command succeeded for %s, but Docker client creation failed: %v", projectName, err) - return fmt.Errorf("failed to create docker client: %w", err) - } - - inspect, _, err := dockerClient.ImageInspectWithRaw(ctx, initialImageTag) - if err != nil { - log.Printf("Build command succeeded for %s in %s, but failed to inspect image '%s': %v", projectName, duration, initialImageTag, err) - log.Printf("Stdout: %s", stdoutBuf.String()) - log.Printf("Stderr: %s", stderrBuf.String()) - return fmt.Errorf("build command succeeded but failed to inspect image %s: %w", initialImageTag, err) - } - - shortID := TruncateID(inspect.ID) - - finalTag := fmt.Sprintf("%s:%s", projectName, shortID) - err = dockerClient.ImageTag(ctx, initialImageTag, finalTag) - if err != nil { - log.Printf("Build succeeded for %s in %s, inspecting image '%s' OK, but failed to tag as '%s': %v", projectName, duration, initialImageTag, finalTag, err) - return fmt.Errorf("failed to tag image %s as %s: %w", initialImageTag, finalTag, err) - } - - state.result = finalTag - log.Printf("Build successful for project: %s. Tagged as: %s (Duration: %s)", projectName, finalTag, duration) - return nil -} - -func TruncateID(id string) string { - shortID := strings.TrimPrefix(id, "sha256:") - if len(shortID) > 12 { - shortID = shortID[:12] - } - return shortID -} diff --git a/kurtosis-devnet/pkg/build/docker_test.go b/kurtosis-devnet/pkg/build/docker_test.go deleted file mode 100644 index ea96e8c2ba30e..0000000000000 --- a/kurtosis-devnet/pkg/build/docker_test.go +++ /dev/null @@ -1,182 +0,0 @@ -package build - -import ( - "bytes" - "context" - "fmt" - "log" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// --- Helper to capture log output --- -func captureLogs(t *testing.T) (*bytes.Buffer, func()) { - var logBuf bytes.Buffer - originalLogger := log.Writer() - log.SetOutput(&logBuf) - t.Cleanup(func() { - log.SetOutput(originalLogger) - }) - return &logBuf, func() { log.SetOutput(originalLogger) } -} - -// --- Tests --- - -func TestDockerBuilder_Build_Success(t *testing.T) { - logBuf, cleanup := captureLogs(t) - defer cleanup() - - projectName := "test-project" - initialTag := "test-project:enclave1" - - // Create a builder in dry run mode - builder := NewDockerBuilder( - WithDockerDryRun(true), - WithDockerConcurrency(1), - ) - - // Execute build - resultTag, err := builder.Build(context.Background(), projectName, initialTag) - - // Verify results - require.NoError(t, err) - assert.Equal(t, initialTag, resultTag) - - // Verify log output - logs := logBuf.String() - assert.Contains(t, logs, fmt.Sprintf("Build started for project: %s (tag: %s)", projectName, initialTag)) - assert.Contains(t, logs, fmt.Sprintf("Dry run: Skipping build for project %s", projectName)) -} - -func TestDockerBuilder_Build_CommandFailure(t *testing.T) { - // Create a builder in dry run mode - builder := NewDockerBuilder( - WithDockerDryRun(true), - WithDockerConcurrency(1), - ) - - // Try to build a project - result, err := builder.Build(context.Background(), "test-project", "test-tag") - - // Verify the result - require.NoError(t, err) - assert.Equal(t, "test-tag", result) -} - -func TestDockerBuilder_Build_ConcurrencyLimit(t *testing.T) { - logBuf, cleanup := captureLogs(t) - defer cleanup() - - concurrencyLimit := 2 - numBuilds := 5 - - // Create a builder in dry run mode with concurrency limit - builder := NewDockerBuilder( - WithDockerDryRun(true), - WithDockerConcurrency(concurrencyLimit), - ) - - // Execute builds concurrently - var wg sync.WaitGroup - wg.Add(numBuilds) - startTime := time.Now() - - for i := 0; i < numBuilds; i++ { - go func(idx int) { - defer wg.Done() - projectName := fmt.Sprintf("concurrent-project-%d", idx) - initialTag := fmt.Sprintf("%s:enclave1", projectName) - _, err := builder.Build(context.Background(), projectName, initialTag) - assert.NoError(t, err, "Build %d failed", idx) - }(i) - } - - wg.Wait() - totalDuration := time.Since(startTime) - - // Verify logs show dry run messages - logs := logBuf.String() - for i := 0; i < numBuilds; i++ { - projectName := fmt.Sprintf("concurrent-project-%d", i) - assert.Contains(t, logs, fmt.Sprintf("Dry run: Skipping build for project %s", projectName)) - } - assert.NotContains(t, logs, "Build failed") - - // Basic check: total time should be reasonable - assert.Less(t, totalDuration, 1*time.Second, "Total duration too long, indicates potential blocking") -} - -func TestDockerBuilder_Build_DryRun(t *testing.T) { - logBuf, cleanup := captureLogs(t) - defer cleanup() - - projectName := "dry-run-project" - initialTag := "dry-run-project:enclave-dry" - - // Create a builder in dry run mode - builder := NewDockerBuilder( - WithDockerDryRun(true), - WithDockerConcurrency(1), - ) - - // Execute build - resultTag, err := builder.Build(context.Background(), projectName, initialTag) - - // Verify results - require.NoError(t, err) - assert.Equal(t, initialTag, resultTag) - - // Verify log output - logs := logBuf.String() - assert.Contains(t, logs, fmt.Sprintf("Build started for project: %s", projectName)) - assert.Contains(t, logs, fmt.Sprintf("Dry run: Skipping build for project %s", projectName)) - assert.NotContains(t, logs, "Executing build command") - assert.NotContains(t, logs, "Build successful") - assert.NotContains(t, logs, "Build failed") -} - -func TestDockerBuilder_Build_DuplicateCalls(t *testing.T) { - logBuf, cleanup := captureLogs(t) - defer cleanup() - - projectName := "duplicate-project" - initialTag := "duplicate:enclave1" - - // Create a builder in dry run mode - builder := NewDockerBuilder( - WithDockerDryRun(true), - WithDockerConcurrency(2), - ) - - // Execute multiple concurrent builds - var wg sync.WaitGroup - numCalls := 3 - results := make([]string, numCalls) - errors := make([]error, numCalls) - wg.Add(numCalls) - - for i := 0; i < numCalls; i++ { - go func(idx int) { - defer wg.Done() - results[idx], errors[idx] = builder.Build(context.Background(), projectName, initialTag) - }(i) - } - - wg.Wait() - - // Verify all calls returned the same result - for i := 0; i < numCalls; i++ { - require.NoError(t, errors[i], "Call %d returned an error", i) - assert.Equal(t, initialTag, results[i], "Call %d returned wrong tag", i) - } - - // Verify logs show dry run messages - logs := logBuf.String() - assert.Contains(t, logs, fmt.Sprintf("Build started for project: %s", projectName)) - assert.Contains(t, logs, fmt.Sprintf("Dry run: Skipping build for project %s", projectName)) - assert.NotContains(t, logs, "Build failed") -} diff --git a/kurtosis-devnet/pkg/build/prestate.go b/kurtosis-devnet/pkg/build/prestate.go deleted file mode 100644 index 38472d5cc35f9..0000000000000 --- a/kurtosis-devnet/pkg/build/prestate.go +++ /dev/null @@ -1,109 +0,0 @@ -package build - -import ( - "bytes" - "context" - "fmt" - "log" - "os/exec" - "text/template" - - "go.opentelemetry.io/otel" -) - -// PrestateBuilder handles building prestates using just commands -type PrestateBuilder struct { - baseDir string - cmdTemplate *template.Template - dryRun bool - - builtPrestates map[string]interface{} -} - -const ( - prestateCmdTemplateStr = "just _prestate-build {{.Path}}" -) - -var defaultPrestateTemplate *template.Template - -func init() { - defaultPrestateTemplate = template.Must(template.New("prestate_build_cmd").Parse(prestateCmdTemplateStr)) -} - -type PrestateBuilderOptions func(*PrestateBuilder) - -func WithPrestateBaseDir(baseDir string) PrestateBuilderOptions { - return func(b *PrestateBuilder) { - b.baseDir = baseDir - } -} - -func WithPrestateTemplate(cmdTemplate *template.Template) PrestateBuilderOptions { - return func(b *PrestateBuilder) { - b.cmdTemplate = cmdTemplate - } -} - -func WithPrestateDryRun(dryRun bool) PrestateBuilderOptions { - return func(b *PrestateBuilder) { - b.dryRun = dryRun - } -} - -// NewPrestateBuilder creates a new PrestateBuilder instance -func NewPrestateBuilder(opts ...PrestateBuilderOptions) *PrestateBuilder { - b := &PrestateBuilder{ - baseDir: ".", - cmdTemplate: defaultPrestateTemplate, - dryRun: false, - builtPrestates: make(map[string]interface{}), - } - - for _, opt := range opts { - opt(b) - } - - return b -} - -// templateData holds the data for the command template -type prestateTemplateData struct { - Path string -} - -// Build executes the prestate build command -func (b *PrestateBuilder) Build(ctx context.Context, path string) error { - _, span := otel.Tracer("prestate-builder").Start(ctx, "build prestate") - defer span.End() - - if _, ok := b.builtPrestates[path]; ok { - return nil - } - - log.Printf("Building prestate: %s", path) - - // Prepare template data - data := prestateTemplateData{ - Path: path, - } - - // Execute template to get command string - var cmdBuf bytes.Buffer - if err := b.cmdTemplate.Execute(&cmdBuf, data); err != nil { - return fmt.Errorf("failed to execute command template: %w", err) - } - - // Create command - cmd := exec.Command("sh", "-c", cmdBuf.String()) - cmd.Dir = b.baseDir - - if !b.dryRun { - output, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("prestate build command failed: %w\nOutput: %s", err, string(output)) - } - } - - b.builtPrestates[path] = struct{}{} - return nil -} diff --git a/kurtosis-devnet/pkg/deploy/deploy.go b/kurtosis-devnet/pkg/deploy/deploy.go deleted file mode 100644 index e938638496572..0000000000000 --- a/kurtosis-devnet/pkg/deploy/deploy.go +++ /dev/null @@ -1,306 +0,0 @@ -package deploy - -import ( - "bytes" - "context" - "fmt" - "io" - "log" - "os" - - ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/enclave" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/engine" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" - autofixTypes "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/types" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/util" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/trace" -) - -type EngineManager interface { - EnsureRunning() error - GetEngineType() (string, error) - RestartEngine() error -} - -type deployer interface { - Deploy(ctx context.Context, input io.Reader) (*spec.EnclaveSpec, error) - GetEnvironmentInfo(ctx context.Context, spec *spec.EnclaveSpec) (*kurtosis.KurtosisEnvironment, error) -} - -type DeployerFunc func(opts ...kurtosis.KurtosisDeployerOptions) (deployer, error) - -type DeployerOption func(*Deployer) - -type Deployer struct { - baseDir string - dryRun bool - kurtosisPkg string - enclave string - kurtosisBinary string - ktDeployer DeployerFunc - engineManager EngineManager - templateFile string - dataFile string - newEnclaveFS func(ctx context.Context, enclave string, opts ...ktfs.EnclaveFSOption) (*ktfs.EnclaveFS, error) - enclaveManager *enclave.KurtosisEnclaveManager - autofixMode autofixTypes.AutofixMode - tracer trace.Tracer -} - -func WithKurtosisDeployer(ktDeployer DeployerFunc) DeployerOption { - return func(d *Deployer) { - d.ktDeployer = ktDeployer - } -} - -func WithEngineManager(engineManager EngineManager) DeployerOption { - return func(d *Deployer) { - d.engineManager = engineManager - } -} - -func WithKurtosisBinary(kurtosisBinary string) DeployerOption { - return func(d *Deployer) { - d.kurtosisBinary = kurtosisBinary - } -} - -func WithKurtosisPackage(kurtosisPkg string) DeployerOption { - return func(d *Deployer) { - d.kurtosisPkg = kurtosisPkg - } -} - -func WithTemplateFile(templateFile string) DeployerOption { - return func(d *Deployer) { - d.templateFile = templateFile - } -} - -func WithDataFile(dataFile string) DeployerOption { - return func(d *Deployer) { - d.dataFile = dataFile - } -} - -func WithBaseDir(baseDir string) DeployerOption { - return func(d *Deployer) { - d.baseDir = baseDir - } -} - -func WithDryRun(dryRun bool) DeployerOption { - return func(d *Deployer) { - d.dryRun = dryRun - } -} - -func WithEnclave(enclave string) DeployerOption { - return func(d *Deployer) { - d.enclave = enclave - } -} - -func WithAutofixMode(autofixMode autofixTypes.AutofixMode) DeployerOption { - return func(d *Deployer) { - d.autofixMode = autofixMode - } -} - -func WithNewEnclaveFSFunc(newEnclaveFS func(ctx context.Context, enclave string, opts ...ktfs.EnclaveFSOption) (*ktfs.EnclaveFS, error)) DeployerOption { - return func(d *Deployer) { - d.newEnclaveFS = newEnclaveFS - } -} - -func NewDeployer(opts ...DeployerOption) (*Deployer, error) { - d := &Deployer{ - kurtosisBinary: "kurtosis", - ktDeployer: func(opts ...kurtosis.KurtosisDeployerOptions) (deployer, error) { - return kurtosis.NewKurtosisDeployer(opts...) - }, - newEnclaveFS: ktfs.NewEnclaveFS, - tracer: otel.Tracer("deployer"), - } - for _, opt := range opts { - opt(d) - } - - if d.engineManager == nil { - d.engineManager = engine.NewEngineManager(engine.WithKurtosisBinary(d.kurtosisBinary)) - } - - if !d.dryRun { - if err := d.engineManager.EnsureRunning(); err != nil { - return nil, fmt.Errorf("error ensuring kurtosis engine is running: %w", err) - } - - // Get and log engine info - engineType, err := d.engineManager.GetEngineType() - if err != nil { - log.Printf("Warning: failed to get engine type: %v", err) - } else { - log.Printf("Kurtosis engine type: %s", engineType) - } - var enclaveManager *enclave.KurtosisEnclaveManager - if engineType == "docker" { - enclaveManager, err = enclave.NewKurtosisEnclaveManager( - enclave.WithDockerManager(&enclave.DefaultDockerManager{}), - ) - } else { - enclaveManager, err = enclave.NewKurtosisEnclaveManager() - } - if err != nil { - return nil, fmt.Errorf("failed to create enclave manager: %w", err) - } - d.enclaveManager = enclaveManager - } else { - // This allows the deployer to work in dry run mode without a running Kurtosis engine - log.Printf("No Kurtosis engine running, skipping enclave manager creation") - } - - return d, nil -} - -func (d *Deployer) deployEnvironment(ctx context.Context, r io.Reader) (*kurtosis.KurtosisEnvironment, error) { - ctx, span := d.tracer.Start(ctx, "deploy environment") - defer span.End() - - // Create a multi reader to output deployment input to stdout - buf := bytes.NewBuffer(nil) - tee := io.TeeReader(r, buf) - - // Log the deployment input - log.Println("Deployment input:") - if _, err := io.Copy(os.Stdout, tee); err != nil { - return nil, fmt.Errorf("error copying deployment input: %w", err) - } - - opts := []kurtosis.KurtosisDeployerOptions{ - kurtosis.WithKurtosisBaseDir(d.baseDir), - kurtosis.WithKurtosisDryRun(d.dryRun), - kurtosis.WithKurtosisPackageName(d.kurtosisPkg), - kurtosis.WithKurtosisEnclave(d.enclave), - kurtosis.WithKurtosisAutofixMode(d.autofixMode), - } - - ktd, err := d.ktDeployer(opts...) - if err != nil { - return nil, fmt.Errorf("error creating kurtosis deployer: %w", err) - } - - spec, err := ktd.Deploy(ctx, buf) - if err != nil { - return nil, fmt.Errorf("error deploying kurtosis package: %w", err) - } - - info, err := ktd.GetEnvironmentInfo(ctx, spec) - if err != nil { - return nil, fmt.Errorf("error getting environment info: %w", err) - } - - // Upload the environment info to the enclave. - fs, err := d.newEnclaveFS(ctx, d.enclave) - if err != nil { - return nil, fmt.Errorf("error getting enclave fs: %w", err) - } - devnetFS := ktfs.NewDevnetFS(fs) - if err := devnetFS.UploadDevnetDescriptor(ctx, info.DevnetEnvironment); err != nil { - return nil, fmt.Errorf("error uploading devnet descriptor: %w", err) - } - - // Only configure Traefik in non-dry-run mode when Docker is available - if !d.dryRun { - if err := util.SetReverseProxyConfig(ctx); err != nil { - return nil, fmt.Errorf("failed to set Traefik network configuration: %w", err) - } - } - - fmt.Printf("Environment running successfully\n") - - return info, nil -} - -func (d *Deployer) renderTemplate(ctx context.Context, buildDir string, urlBuilder func(path ...string) string) (*bytes.Buffer, error) { - ctx, span := d.tracer.Start(ctx, "render template") - defer span.End() - - t := &Templater{ - baseDir: d.baseDir, - dryRun: d.dryRun, - enclave: d.enclave, - templateFile: d.templateFile, - dataFile: d.dataFile, - enclaveManager: d.enclaveManager, - buildDir: buildDir, - urlBuilder: urlBuilder, - } - - return t.Render(ctx) -} - -func (d *Deployer) Deploy(ctx context.Context, r io.Reader) (*kurtosis.KurtosisEnvironment, error) { - ctx, span := d.tracer.Start(ctx, "deploy devnet") - defer span.End() - - // Clean up the enclave before deploying - if d.autofixMode == autofixTypes.AutofixModeNuke { - // Recreate the engine - log.Println("Restarting engine") - if err := d.engineManager.RestartEngine(); err != nil { - return nil, fmt.Errorf("error restarting engine: %w", err) - } - log.Println("Nuking enclave") - if d.enclaveManager != nil { - // Remove all the enclaves and destroy all the docker resources related to kurtosis - err := d.enclaveManager.Nuke(ctx) - if err != nil { - return nil, fmt.Errorf("error nuking enclave: %w", err) - } - } - } else if d.autofixMode == autofixTypes.AutofixModeNormal { - log.Println("Autofixing enclave") - if d.enclaveManager != nil { - if err := d.enclaveManager.Autofix(ctx, d.enclave); err != nil { - return nil, fmt.Errorf("error autofixing enclave: %w", err) - } - } - } - - // Pre-create the enclave if it doesn't exist - if d.enclaveManager != nil { - _, err := d.enclaveManager.GetEnclave(ctx, d.enclave) - if err != nil { - return nil, fmt.Errorf("error getting enclave: %w", err) - } - } - - tmpDir, err := os.MkdirTemp("", d.enclave) - if err != nil { - return nil, fmt.Errorf("error creating temporary directory: %w", err) - } - defer os.RemoveAll(tmpDir) - - srv := &FileServer{ - baseDir: d.baseDir, - dryRun: d.dryRun, - enclave: d.enclave, - deployer: d.ktDeployer, - } - - ch := srv.getState(ctx) - - buf, err := d.renderTemplate(ctx, tmpDir, srv.URL) - if err != nil { - return nil, fmt.Errorf("error rendering template: %w", err) - } - - if err := srv.Deploy(ctx, tmpDir, ch); err != nil { - return nil, fmt.Errorf("error deploying fileserver: %w", err) - } - - return d.deployEnvironment(ctx, buf) -} diff --git a/kurtosis-devnet/pkg/deploy/deploy_test.go b/kurtosis-devnet/pkg/deploy/deploy_test.go deleted file mode 100644 index 0836e870d2cfe..0000000000000 --- a/kurtosis-devnet/pkg/deploy/deploy_test.go +++ /dev/null @@ -1,143 +0,0 @@ -package deploy - -import ( - "bytes" - "context" - "encoding/json" - "io" - "os" - "path/filepath" - "testing" - - ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" - "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// mockDeployerForTest implements the deployer interface for testing -type mockDeployerForTest struct { - baseDir string -} - -func (m *mockDeployerForTest) Deploy(ctx context.Context, input io.Reader) (*spec.EnclaveSpec, error) { - // Create a mock env.json file - envPath := filepath.Join(m.baseDir, "env.json") - mockEnv := map[string]interface{}{ - "test": "value", - } - data, err := json.Marshal(mockEnv) - if err != nil { - return nil, err - } - if err := os.WriteFile(envPath, data, 0644); err != nil { - return nil, err - } - return &spec.EnclaveSpec{}, nil -} - -func (m *mockDeployerForTest) GetEnvironmentInfo(ctx context.Context, spec *spec.EnclaveSpec) (*kurtosis.KurtosisEnvironment, error) { - return &kurtosis.KurtosisEnvironment{}, nil -} - -// mockEnclaveContext implements EnclaveContextIface for testing -type mockEnclaveContext struct { - artifacts []string -} - -func (m *mockEnclaveContext) GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) { - result := make([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, len(m.artifacts)) - for i, name := range m.artifacts { - result[i] = &kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid{ - FileName: name, - FileUuid: "test-uuid", - } - } - return result, nil -} - -func (m *mockEnclaveContext) DownloadFilesArtifact(ctx context.Context, name string) ([]byte, error) { - return nil, nil -} - -func (m *mockEnclaveContext) UploadFiles(pathToUpload string, artifactName string) (services.FilesArtifactUUID, services.FileArtifactName, error) { - return "", "", nil -} - -func TestDeploy(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // Create a temporary directory for the environment output - tmpDir, err := os.MkdirTemp("", "deploy-test") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - // Create a simple template file - templatePath := filepath.Join(tmpDir, "template.yaml") - err = os.WriteFile(templatePath, []byte("test: {{ .Config }}"), 0644) - require.NoError(t, err) - - // Create a simple data file - dataPath := filepath.Join(tmpDir, "data.json") - err = os.WriteFile(dataPath, []byte(`{"Config": "value"}`), 0644) - require.NoError(t, err) - - envPath := filepath.Join(tmpDir, "env.json") - // Create a simple deployment configuration - deployConfig := bytes.NewBufferString(`{"test": "config"}`) - - // Create a mock deployer function - mockDeployerFunc := func(opts ...kurtosis.KurtosisDeployerOptions) (deployer, error) { - return &mockDeployerForTest{baseDir: tmpDir}, nil - } - - // Create a mock EnclaveFS function - mockEnclaveFSFunc := func(ctx context.Context, enclave string, opts ...ktfs.EnclaveFSOption) (*ktfs.EnclaveFS, error) { - mockCtx := &mockEnclaveContext{ - artifacts: []string{ - "devnet-descriptor-1", - "devnet-descriptor-2", - }, - } - return ktfs.NewEnclaveFS(ctx, enclave, ktfs.WithEnclaveCtx(mockCtx)) - } - - d, err := NewDeployer( - WithBaseDir(tmpDir), - WithKurtosisDeployer(mockDeployerFunc), - WithDryRun(true), - WithTemplateFile(templatePath), - WithDataFile(dataPath), - WithNewEnclaveFSFunc(mockEnclaveFSFunc), - ) - require.NoError(t, err) - - env, err := d.Deploy(ctx, deployConfig) - require.NoError(t, err) - require.NotNil(t, env) - - // Verify the environment file was created - assert.FileExists(t, envPath) - - // Read and verify the content - content, err := os.ReadFile(envPath) - require.NoError(t, err) - - var envData map[string]interface{} - err = json.Unmarshal(content, &envData) - require.NoError(t, err) - assert.Equal(t, "value", envData["test"]) -} - -func TestNewDeployer_DryRun(t *testing.T) { - // In dry run mode, we should not create an enclave manager - deployer, err := NewDeployer( - WithDryRun(true), - ) - require.NoError(t, err) - assert.Nil(t, deployer.enclaveManager) -} diff --git a/kurtosis-devnet/pkg/deploy/fileserver.go b/kurtosis-devnet/pkg/deploy/fileserver.go deleted file mode 100644 index 53b0802442cf3..0000000000000 --- a/kurtosis-devnet/pkg/deploy/fileserver.go +++ /dev/null @@ -1,282 +0,0 @@ -package deploy - -import ( - "bytes" - "context" - "crypto/sha256" - "encoding/hex" - "encoding/json" - "fmt" - "log" - "os" - "path/filepath" - "strings" - "sync" - - ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/util" - "github.com/spf13/afero" - "go.opentelemetry.io/otel" -) - -const FILESERVER_PACKAGE = "fileserver" - -type FileServer struct { - baseDir string - enclave string - dryRun bool - deployer DeployerFunc - fs afero.Fs -} - -func (f *FileServer) URL(path ...string) string { - return fmt.Sprintf("http://%s/%s", FILESERVER_PACKAGE, strings.Join(path, "/")) -} - -func (f *FileServer) Deploy(ctx context.Context, sourceDir string, stateCh <-chan *fileserverState) (retErr error) { - ctx, span := otel.Tracer("fileserver").Start(ctx, "deploy fileserver") - defer span.End() - - if f.fs == nil { - f.fs = afero.NewOsFs() - } - - // Check if source directory is empty. If it is, then ie means we don't have - // anything to serve, so we might as well not deploy the fileserver. - entries, err := afero.ReadDir(f.fs, sourceDir) - if err != nil { - return fmt.Errorf("error reading source directory: %w", err) - } - if len(entries) == 0 { - return nil - } - - srcHash, err := calculateDirHashWithFs(sourceDir, f.fs) - if err != nil { - return fmt.Errorf("error calculating source directory hash: %w", err) - } - - // Create a temp dir in the fileserver package - baseDir := filepath.Join(f.baseDir, FILESERVER_PACKAGE) - if err := f.fs.MkdirAll(baseDir, 0755); err != nil { - return fmt.Errorf("error creating base directory: %w", err) - } - - // Create the nginx directory structure - nginxDir := filepath.Join(baseDir, "static_files", "nginx") - if err := f.fs.MkdirAll(nginxDir, 0755); err != nil { - return fmt.Errorf("error creating nginx directory: %w", err) - } - - configHash, err := calculateDirHashWithFs(nginxDir, f.fs) - if err != nil { - return fmt.Errorf("error calculating base directory hash: %w", err) - } - - refState := <-stateCh - if refState.contentHash == srcHash && refState.configHash == configHash { - log.Println("No changes to fileserver, skipping deployment") - return nil - } - - // Can't use MkdirTemp here because the directory name needs to always be the same - // in order for kurtosis file artifact upload to be idempotent. - // (i.e. the file upload and all its downstream dependencies can be SKIPPED on re-runs) - tempDir := filepath.Join(baseDir, "upload-content") - - // Clean up any existing content - if err := f.fs.RemoveAll(tempDir); err != nil { - return fmt.Errorf("error cleaning up existing directory: %w", err) - } - - // Create the directory - if err := f.fs.MkdirAll(tempDir, 0755); err != nil { - return fmt.Errorf("error creating temporary directory: %w", err) - } - defer func() { - if err := f.fs.RemoveAll(tempDir); err != nil && retErr == nil { - retErr = fmt.Errorf("error cleaning up temporary directory: %w", err) - } - }() - - // Copy build dir contents to tempDir - if err := util.CopyDir(sourceDir, tempDir, f.fs); err != nil { - return fmt.Errorf("error copying directory: %w", err) - } - - buf := bytes.NewBuffer(nil) - buf.WriteString(fmt.Sprintf("source_path: %s\n", filepath.Base(tempDir))) - - opts := []kurtosis.KurtosisDeployerOptions{ - kurtosis.WithKurtosisBaseDir(f.baseDir), - kurtosis.WithKurtosisDryRun(f.dryRun), - kurtosis.WithKurtosisPackageName(FILESERVER_PACKAGE), - kurtosis.WithKurtosisEnclave(f.enclave), - } - - d, err := f.deployer(opts...) - if err != nil { - return fmt.Errorf("error creating kurtosis deployer: %w", err) - } - - _, err = d.Deploy(ctx, buf) - if err != nil { - return fmt.Errorf("error deploying kurtosis package: %w", err) - } - - return -} - -type fileserverState struct { - contentHash string - configHash string -} - -// downloadAndHashArtifact downloads an artifact and calculates its hash -func downloadAndHashArtifact(ctx context.Context, enclave, artifactName string) (hash string, retErr error) { - fs, err := ktfs.NewEnclaveFS(ctx, enclave) - if err != nil { - return "", fmt.Errorf("failed to create enclave fs: %w", err) - } - - // Create temp dir - osFs := afero.NewOsFs() - tempDir, err := afero.TempDir(osFs, "", artifactName+"-*") - if err != nil { - return "", fmt.Errorf("failed to create temp dir: %w", err) - } - defer func() { - if err := osFs.RemoveAll(tempDir); err != nil && retErr == nil { - retErr = fmt.Errorf("error cleaning up temporary directory: %w", err) - } - }() - - // Download artifact - artifact, err := fs.GetArtifact(ctx, artifactName) - if err != nil { - return "", fmt.Errorf("failed to get artifact: %w", err) - } - - // Ensure parent directories exist before extracting - if err := osFs.MkdirAll(tempDir, 0755); err != nil { - return "", fmt.Errorf("failed to create temp dir structure: %w", err) - } - - // Extract to temp dir - if err := artifact.Download(tempDir); err != nil { - return "", fmt.Errorf("failed to download artifact: %w", err) - } - - // Calculate hash - hash, err = calculateDirHash(tempDir) - if err != nil { - return "", fmt.Errorf("failed to calculate hash: %w", err) - } - - return -} - -func (f *FileServer) getState(ctx context.Context) <-chan *fileserverState { - stateCh := make(chan *fileserverState) - - go func(ctx context.Context) { - st := &fileserverState{} - var wg sync.WaitGroup - - type artifactInfo struct { - name string - dest *string - } - - artifacts := []artifactInfo{ - {"fileserver-content", &st.contentHash}, - {"fileserver-nginx-conf", &st.configHash}, - } - - for _, art := range artifacts { - wg.Add(1) - go func(art artifactInfo) { - defer wg.Done() - hash, err := downloadAndHashArtifact(ctx, f.enclave, art.name) - if err == nil { - *art.dest = hash - } - }(art) - } - - wg.Wait() - stateCh <- st - }(ctx) - - return stateCh -} - -type entry struct { - RelPath string `json:"rel_path"` - Size int64 `json:"size"` - Mode string `json:"mode"` - Content []byte `json:"content"` -} - -// calculateDirHash returns a SHA256 hash of the directory contents -// It walks through the directory, hashing file names and contents -func calculateDirHash(dir string) (string, error) { - return calculateDirHashWithFs(dir, afero.NewOsFs()) -} - -// calculateDirHashWithFs is like calculateDirHash but accepts a custom filesystem -func calculateDirHashWithFs(dir string, fs afero.Fs) (string, error) { - hash := sha256.New() - - err := afero.Walk(fs, dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - // Get path relative to root dir - relPath, err := filepath.Rel(dir, path) - if err != nil { - return err - } - - // Skip the root directory - if relPath == "." { - return nil - } - - // Add the relative path and file info to hash - entry := entry{ - RelPath: relPath, - Size: info.Size(), - Mode: info.Mode().String(), - } - - // If it's a regular file, add its contents to hash - if !info.IsDir() { - content, err := afero.ReadFile(fs, path) - if err != nil { - return err - } - entry.Content = content - } - - jsonBytes, err := json.Marshal(entry) - if err != nil { - return err - } - _, err = hash.Write(jsonBytes) - if err != nil { - return err - } - - return nil - }) - - if err != nil { - return "", fmt.Errorf("error walking directory: %w", err) - } - - hashStr := hex.EncodeToString(hash.Sum(nil)) - return hashStr, nil -} diff --git a/kurtosis-devnet/pkg/deploy/fileserver_test.go b/kurtosis-devnet/pkg/deploy/fileserver_test.go deleted file mode 100644 index 75afe3a9637a5..0000000000000 --- a/kurtosis-devnet/pkg/deploy/fileserver_test.go +++ /dev/null @@ -1,149 +0,0 @@ -package deploy - -import ( - "context" - "io" - "path/filepath" - "testing" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestDeployFileserver(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - fs := afero.NewMemMapFs() - - // Create test directories - sourceDir := "/source" - require.NoError(t, fs.MkdirAll(sourceDir, 0755)) - - // Create required directory structure - nginxDir := filepath.Join(sourceDir, "static_files", "nginx") - require.NoError(t, fs.MkdirAll(nginxDir, 0755)) - - // Create a mock deployer function - mockDeployerFunc := func(opts ...kurtosis.KurtosisDeployerOptions) (deployer, error) { - return &mockDeployer{}, nil - } - - testCases := []struct { - name string - setup func(t *testing.T, fs afero.Fs, sourceDir, nginxDir string, state *fileserverState) - state *fileserverState - shouldError bool - shouldDeploy bool - }{ - { - name: "empty source directory - no deployment needed", - setup: func(t *testing.T, fs afero.Fs, sourceDir, nginxDir string, state *fileserverState) { - // No files to create - }, - state: &fileserverState{}, - shouldError: false, - shouldDeploy: false, - }, - { - name: "new files to deploy", - setup: func(t *testing.T, fs afero.Fs, sourceDir, nginxDir string, state *fileserverState) { - require.NoError(t, afero.WriteFile( - fs, - filepath.Join(sourceDir, "test.txt"), - []byte("test content"), - 0644, - )) - }, - state: &fileserverState{}, - shouldError: false, - shouldDeploy: true, - }, - { - name: "no changes - deployment skipped", - setup: func(t *testing.T, fs afero.Fs, sourceDir, nginxDir string, state *fileserverState) { - require.NoError(t, afero.WriteFile( - fs, - filepath.Join(sourceDir, "test.txt"), - []byte("test content"), - 0644, - )) - - // Calculate actual hash for the test file - hash, err := calculateDirHashWithFs(sourceDir, fs) - require.NoError(t, err) - - // Calculate nginx config hash - configHash, err := calculateDirHashWithFs(nginxDir, fs) - require.NoError(t, err) - - // Update state with actual hashes - state.contentHash = hash - state.configHash = configHash - }, - state: &fileserverState{}, - shouldError: false, - shouldDeploy: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Clean up and recreate source directory for each test - require.NoError(t, fs.RemoveAll(sourceDir)) - require.NoError(t, fs.MkdirAll(sourceDir, 0755)) - - // Recreate nginx directory - require.NoError(t, fs.MkdirAll(nginxDir, 0755)) - - // Setup test files - tc.setup(t, fs, sourceDir, nginxDir, tc.state) - - // Create a separate directory for the fileserver deployment - deployBaseDir := "/deploy" - require.NoError(t, fs.MkdirAll(deployBaseDir, 0755)) - - fileServer := &FileServer{ - baseDir: deployBaseDir, - enclave: "test-enclave", - dryRun: true, - deployer: mockDeployerFunc, - fs: fs, - } - - // Create state channel and send test state - ch := make(chan *fileserverState, 1) - ch <- tc.state - close(ch) - - err := fileServer.Deploy(ctx, sourceDir, ch) - if tc.shouldError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - - // Verify deployment directory was created only if deployment was needed - deployDir := filepath.Join(deployBaseDir, FILESERVER_PACKAGE) - exists, err := afero.Exists(fs, deployDir) - require.NoError(t, err) - if tc.shouldDeploy { - assert.True(t, exists) - } - }) - } -} - -// mockDeployer implements the deployer interface for testing -type mockDeployer struct{} - -func (m *mockDeployer) Deploy(ctx context.Context, input io.Reader) (*spec.EnclaveSpec, error) { - return &spec.EnclaveSpec{}, nil -} - -func (m *mockDeployer) GetEnvironmentInfo(ctx context.Context, spec *spec.EnclaveSpec) (*kurtosis.KurtosisEnvironment, error) { - return &kurtosis.KurtosisEnvironment{}, nil -} diff --git a/kurtosis-devnet/pkg/deploy/prestate.go b/kurtosis-devnet/pkg/deploy/prestate.go deleted file mode 100644 index d6c7a823a6c98..0000000000000 --- a/kurtosis-devnet/pkg/deploy/prestate.go +++ /dev/null @@ -1,112 +0,0 @@ -package deploy - -import ( - "context" - "encoding/json" - "fmt" - "log" - "os" - "path/filepath" - "strings" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/build" -) - -type PrestateInfo struct { - URL string `json:"url"` - Hashes map[string]string `json:"hashes"` -} - -type localPrestateHolder struct { - info *PrestateInfo - baseDir string - buildDir string - dryRun bool - builder *build.PrestateBuilder - urlBuilder func(path ...string) string -} - -func (h *localPrestateHolder) GetPrestateInfo(ctx context.Context) (*PrestateInfo, error) { - if h.info != nil { - return h.info, nil - } - - prestatePath := []string{"proofs", "op-program", "cannon"} - prestateURL := h.urlBuilder(prestatePath...) - - // Create build directory with the final path structure - buildDir := filepath.Join(append([]string{h.buildDir}, prestatePath...)...) - if err := os.MkdirAll(buildDir, 0755); err != nil { - return nil, fmt.Errorf("failed to create prestate build directory: %w", err) - } - - info := &PrestateInfo{ - URL: prestateURL, - Hashes: make(map[string]string), - } - - if h.dryRun { - // In dry run, populate with placeholder keys to avoid template errors during first pass - info.Hashes["prestate_mt64"] = "dry_run_placeholder" - info.Hashes["prestate_interop"] = "dry_run_placeholder" - h.info = info - return info, nil - } - - // Map of known file prefixes to their keys - fileToKey := map[string]string{ - "prestate-proof-mt64.json": "prestate_mt64", - "prestate-proof-interop.json": "prestate_interop", - } - - // Build all prestate files directly in the target directory - if err := h.builder.Build(ctx, buildDir); err != nil { - return nil, fmt.Errorf("failed to build prestates: %w", err) - } - - // Find and process all prestate files - matches, err := filepath.Glob(filepath.Join(buildDir, "prestate-proof*.json")) - if err != nil { - return nil, fmt.Errorf("failed to find prestate files: %w", err) - } - - // Process each file to rename it to its hash - for _, filePath := range matches { - content, err := os.ReadFile(filePath) - if err != nil { - return nil, fmt.Errorf("failed to read prestate %s: %w", filepath.Base(filePath), err) - } - - var data struct { - Pre string `json:"pre"` - } - if err := json.Unmarshal(content, &data); err != nil { - return nil, fmt.Errorf("failed to parse prestate %s: %w", filepath.Base(filePath), err) - } - - // Store hash with its corresponding key - if key, exists := fileToKey[filepath.Base(filePath)]; exists { - info.Hashes[key] = data.Pre - } - - // Rename files to hash-based names - newFileName := data.Pre + ".json" - hashedPath := filepath.Join(buildDir, newFileName) - if err := os.Rename(filePath, hashedPath); err != nil { - return nil, fmt.Errorf("failed to rename prestate %s: %w", filepath.Base(filePath), err) - } - log.Printf("%s available at: %s/%s\n", filepath.Base(filePath), prestateURL, newFileName) - - // Rename the corresponding binary file - binFilePath := strings.Replace(strings.TrimSuffix(filePath, ".json"), "-proof", "", 1) + ".bin.gz" - newBinFileName := data.Pre + ".bin.gz" - binHashedPath := filepath.Join(buildDir, newBinFileName) - if err := os.Rename(binFilePath, binHashedPath); err != nil { - return nil, fmt.Errorf("failed to rename prestate %s: %w", filepath.Base(binFilePath), err) - } - log.Printf("%s available at: %s/%s\n", filepath.Base(binFilePath), prestateURL, newBinFileName) - } - - h.info = info - return info, nil -} diff --git a/kurtosis-devnet/pkg/deploy/prestate_test.go b/kurtosis-devnet/pkg/deploy/prestate_test.go deleted file mode 100644 index 3525531547aa4..0000000000000 --- a/kurtosis-devnet/pkg/deploy/prestate_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package deploy - -import ( - "bytes" - "context" - "os" - "path/filepath" - "strings" - "sync" - "testing" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/tmpl" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" -) - -func TestLocalPrestate(t *testing.T) { - tests := []struct { - name string - dryRun bool - wantErr bool - }{ - { - name: "dry run mode", - dryRun: true, - wantErr: false, - }, - { - name: "normal mode", - dryRun: false, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "prestate-test") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - // Create a mock justfile for each test case - err = os.WriteFile(filepath.Join(tmpDir, "justfile"), []byte(` -_prestate-build target: - @echo "Mock prestate build" -`), 0644) - require.NoError(t, err) - - templater := &Templater{ - baseDir: tmpDir, - dryRun: tt.dryRun, - buildDir: tmpDir, - urlBuilder: func(path ...string) string { - return "http://fileserver/" + strings.Join(path, "/") - }, - } - - buildWg := &sync.WaitGroup{} - // Create template context with just the prestate function - tmplCtx := tmpl.NewTemplateContext(templater.localPrestateOption(context.Background(), buildWg)) - - // Test template with multiple calls to localPrestate - template := `first: - url: {{(localPrestate).URL}} - hashes: - game: {{index (localPrestate).Hashes "game"}} - proof: {{index (localPrestate).Hashes "proof"}} -second: - url: {{(localPrestate).URL}} - hashes: - game: {{index (localPrestate).Hashes "game"}} - proof: {{index (localPrestate).Hashes "proof"}}` - buf := bytes.NewBuffer(nil) - err = tmplCtx.InstantiateTemplate(bytes.NewBufferString(template), buf) - - if tt.wantErr { - assert.Error(t, err) - return - } - require.NoError(t, err) - - // Wait for the async goroutine to complete - buildWg.Wait() - - // Verify the output is valid YAML and contains the static path - output := buf.String() - assert.Contains(t, output, "url: http://fileserver/proofs/op-program/cannon") - - // Verify both calls return the same values - var result struct { - First struct { - URL string `yaml:"url"` - Hashes map[string]string `yaml:"hashes"` - } `yaml:"first"` - Second struct { - URL string `yaml:"url"` - Hashes map[string]string `yaml:"hashes"` - } `yaml:"second"` - } - err = yaml.Unmarshal(buf.Bytes(), &result) - require.NoError(t, err) - - // Check that both calls returned identical results - assert.Equal(t, result.First.URL, result.Second.URL, "URLs should match") - assert.Equal(t, result.First.Hashes, result.Second.Hashes, "Hashes should match") - - // In dry run mode, we don't create the directory - prestateDir := filepath.Join(tmpDir, "proofs", "op-program", "cannon") - assert.DirExists(t, prestateDir) - }) - } -} diff --git a/kurtosis-devnet/pkg/deploy/template.go b/kurtosis-devnet/pkg/deploy/template.go deleted file mode 100644 index d1c509a535c35..0000000000000 --- a/kurtosis-devnet/pkg/deploy/template.go +++ /dev/null @@ -1,288 +0,0 @@ -package deploy - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "log" - "os" - "strings" - "sync" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/build" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/enclave" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/tmpl" -) - -var ( - dockerBuildConcurrency = 8 -) - -type Templater struct { - enclave string - dryRun bool - baseDir string - templateFile string - dataFile string - buildDir string - urlBuilder func(path ...string) string - - // Common state across template functions - buildJobsMux sync.Mutex - buildJobs map[string]*dockerBuildJob - - contracts contractStateBuildJob - prestate prestateStateBuildJob - - enclaveManager *enclave.KurtosisEnclaveManager -} - -// prestateStateBuildJob helps track the state of the prestate build -type prestateStateBuildJob struct { - info *PrestateInfo - err error - started bool -} - -// contractStateBuildJob helps track the state of the contract build -type contractStateBuildJob struct { - url string - err error - started bool -} - -// dockerBuildJob helps collect and group build jobs -type dockerBuildJob struct { - projectName string - imageTag string - result string - err error - done chan struct{} -} - -func (f *Templater) localDockerImageOption(_ context.Context) tmpl.TemplateContextOptions { - // Initialize the build jobs map if it's nil - if f.buildJobs == nil { - f.buildJobs = make(map[string]*dockerBuildJob) - } - - imageTag := func(projectName string) string { - return fmt.Sprintf("%s:%s", projectName, f.enclave) - } - - // Function that gets called during template rendering - return tmpl.WithFunction("localDockerImage", func(projectName string) (string, error) { - tag := imageTag(projectName) - - // First, check if we already have this build job - f.buildJobsMux.Lock() - job, exists := f.buildJobs[projectName] - if !exists { - // If not, create a new job but don't start it yet - job = &dockerBuildJob{ - projectName: projectName, - imageTag: tag, - done: make(chan struct{}), - } - f.buildJobs[projectName] = job - } - f.buildJobsMux.Unlock() - - // If the job is already done, return its result - select { - case <-job.done: - return job.result, job.err - default: - // Just collect the build request for now and return a placeholder - // The actual build will happen in Render() before final template evaluation - return fmt.Sprintf("__PLACEHOLDER_DOCKER_IMAGE_%s__", projectName), nil - } - }) -} - -func (f *Templater) localContractArtifactsOption(ctx context.Context, buildWg *sync.WaitGroup) tmpl.TemplateContextOptions { - contractBuilder := build.NewContractBuilder( - build.WithContractBaseDir(f.baseDir), - build.WithContractDryRun(f.dryRun), - build.WithContractEnclave(f.enclave), - build.WithContractEnclaveManager(f.enclaveManager), - ) - - return tmpl.WithFunction("localContractArtifacts", func(layer string) (string, error) { - if f.dryRun { - return "artifact://contracts", nil - } - if !f.contracts.started { - f.contracts.started = true - buildWg.Add(1) - go func() { - url, err := contractBuilder.Build(ctx, "") - f.contracts.url = url - f.contracts.err = err - buildWg.Done() - }() - return contractBuilder.GetContractUrl(), nil - } - return f.contracts.url, f.contracts.err - }) -} - -func (f *Templater) localPrestateOption(ctx context.Context, buildWg *sync.WaitGroup) tmpl.TemplateContextOptions { - holder := &localPrestateHolder{ - baseDir: f.baseDir, - buildDir: f.buildDir, - dryRun: f.dryRun, - builder: build.NewPrestateBuilder( - build.WithPrestateBaseDir(f.baseDir), - build.WithPrestateDryRun(f.dryRun), - ), - urlBuilder: f.urlBuilder, - } - - return tmpl.WithFunction("localPrestate", func() (*PrestateInfo, error) { - if !f.prestate.started { - f.prestate.started = true - buildWg.Add(1) - go func() { - info, err := holder.GetPrestateInfo(ctx) - f.prestate.info = info - f.prestate.err = err - buildWg.Done() - }() - } - if f.prestate.info == nil { - prestatePath := []string{"proofs", "op-program", "cannon"} - return &PrestateInfo{ - URL: f.urlBuilder(prestatePath...), - Hashes: map[string]string{ - "prestate_mt64": "dry_run_placeholder", - "prestate_interop": "dry_run_placeholder", - }, - }, nil - } - return f.prestate.info, f.prestate.err - }) -} - -func (f *Templater) Render(ctx context.Context) (*bytes.Buffer, error) { - // Initialize the build jobs map if it's nil - if f.buildJobs == nil { - f.buildJobs = make(map[string]*dockerBuildJob) - } - - // Check if template file exists - if _, err := os.Stat(f.templateFile); os.IsNotExist(err) { - return nil, fmt.Errorf("template file does not exist: %s", f.templateFile) - } - - // Check if the template file contains template syntax - content, err := os.ReadFile(f.templateFile) - if err != nil { - return nil, fmt.Errorf("error reading template file: %w", err) - } - - if len(content) == 0 { - return nil, fmt.Errorf("template file is empty: %s", f.templateFile) - } - - contentStr := string(content) - if !strings.Contains(contentStr, "{{") && !strings.Contains(contentStr, "}}") { - // This is a plain YAML file, return it as-is - return bytes.NewBuffer(content), nil - } - - buildWg := &sync.WaitGroup{} - - opts := []tmpl.TemplateContextOptions{ - f.localDockerImageOption(ctx), - f.localContractArtifactsOption(ctx, buildWg), - f.localPrestateOption(ctx, buildWg), - tmpl.WithBaseDir(f.baseDir), - } - - // Read and parse the data file if provided - if f.dataFile != "" { - data, err := os.ReadFile(f.dataFile) - if err != nil { - return nil, fmt.Errorf("error reading data file: %w", err) - } - - var templateData map[string]interface{} - if err := json.Unmarshal(data, &templateData); err != nil { - return nil, fmt.Errorf("error parsing JSON data: %w", err) - } - - opts = append(opts, tmpl.WithData(templateData)) - } - - // Open template file - tmplFile, err := os.Open(f.templateFile) - if err != nil { - return nil, fmt.Errorf("error opening template file: %w", err) - } - defer tmplFile.Close() - - // Create template context - tmplCtx := tmpl.NewTemplateContext(opts...) - - // First pass: Collect all build jobs without executing them - prelimBuf := bytes.NewBuffer(nil) - if err := tmplCtx.InstantiateTemplate(tmplFile, prelimBuf); err != nil { - return nil, fmt.Errorf("error in first-pass template processing: %w", err) - } - - // Find all docker build jobs and execute them concurrently - var dockerJobs []*dockerBuildJob - f.buildJobsMux.Lock() - for _, job := range f.buildJobs { - dockerJobs = append(dockerJobs, job) - } - f.buildJobsMux.Unlock() - - if len(dockerJobs) > 0 { - // Create a single Docker builder for all builds using the factory - dockerBuilder := build.NewDockerBuilder( - build.WithDockerBaseDir(f.baseDir), - build.WithDockerDryRun(f.dryRun), - build.WithDockerConcurrency(dockerBuildConcurrency), // Set concurrency - ) - - // Start all the builds - buildWg.Add(len(dockerJobs)) - for _, job := range dockerJobs { - go func(j *dockerBuildJob) { - defer buildWg.Done() - log.Printf("Starting build for %s (tag: %s)", j.projectName, j.imageTag) - j.result, j.err = dockerBuilder.Build(ctx, j.projectName, j.imageTag) - close(j.done) // Mark this job as done - }(job) - } - buildWg.Wait() // Wait for all builds to complete - - // Check for any build errors - for _, job := range dockerJobs { - if job.err != nil { - return nil, fmt.Errorf("error building docker image for %s: %w", job.projectName, job.err) - } - } - - // Now reopen the template file for the second pass - tmplFile.Close() - tmplFile, err = os.Open(f.templateFile) - if err != nil { - return nil, fmt.Errorf("error reopening template file: %w", err) - } - defer tmplFile.Close() - } else { - buildWg.Wait() - } - - // Second pass: Render with actual build results - buf := bytes.NewBuffer(nil) - if err := tmplCtx.InstantiateTemplate(tmplFile, buf); err != nil { - return nil, fmt.Errorf("error processing template: %w", err) - } - - return buf, nil -} diff --git a/kurtosis-devnet/pkg/deploy/template_test.go b/kurtosis-devnet/pkg/deploy/template_test.go deleted file mode 100644 index 7e7b9e2d9bc75..0000000000000 --- a/kurtosis-devnet/pkg/deploy/template_test.go +++ /dev/null @@ -1,417 +0,0 @@ -package deploy - -import ( - "context" - "os" - "path/filepath" - "strings" - "sync" - "testing" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/enclave" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/tmpl" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestRenderTemplate(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "template-test") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - // Create a test template file - templateContent := ` -name: {{.name}} -image: {{localDockerImage "test-project"}} -artifacts: {{localContractArtifacts "l1"}}` - - templatePath := filepath.Join(tmpDir, "template.yaml") - err = os.WriteFile(templatePath, []byte(templateContent), 0644) - require.NoError(t, err) - - // Create a test data file - dataContent := `{"name": "test-deployment"}` - dataPath := filepath.Join(tmpDir, "data.json") - err = os.WriteFile(dataPath, []byte(dataContent), 0644) - require.NoError(t, err) - - // Create a Templater instance - templater := &Templater{ - enclave: "test-enclave", - dryRun: true, - baseDir: tmpDir, - templateFile: templatePath, - dataFile: dataPath, - buildDir: tmpDir, - urlBuilder: func(path ...string) string { - return "http://localhost:8080/" + strings.Join(path, "/") - }, - } - - buf, err := templater.Render(context.Background()) - require.NoError(t, err) - - // Verify template rendering - assert.Contains(t, buf.String(), "test-deployment") - assert.Contains(t, buf.String(), "test-project:test-enclave") -} - -func TestRenderTemplate_DryRun(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "template-test-dryrun") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - // Create a test template file with multiple docker image requests, including duplicates - templateContent := ` -name: {{.name}} -imageA1: {{ localDockerImage "project-a" }} -imageB: {{ localDockerImage "project-b" }} -imageA2: {{ localDockerImage "project-a" }} -contracts: {{ localContractArtifacts "l1" }} -prestateHash: {{ (localPrestate).Hashes.prestate_mt64 }}` - - templatePath := filepath.Join(tmpDir, "template.yaml") - err = os.WriteFile(templatePath, []byte(templateContent), 0644) - require.NoError(t, err) - - // Create a test data file - dataContent := `{"name": "test-deployment"}` - dataPath := filepath.Join(tmpDir, "data.json") - err = os.WriteFile(dataPath, []byte(dataContent), 0644) - require.NoError(t, err) - - // Create dummy prestate and contract files for dry run build simulation - prestateDir := filepath.Join(tmpDir, "prestate_build") - contractsDir := filepath.Join(tmpDir, "contracts_build") - require.NoError(t, os.MkdirAll(prestateDir, 0755)) - require.NoError(t, os.MkdirAll(contractsDir, 0755)) - // Note: The actual content doesn't matter for dry run, just existence might - // depending on how the builders are implemented, but our current focus is docker build flow. - - // Create a Templater instance in dryRun mode - enclaveName := "test-enclave-dryrun" - templater := &Templater{ - enclave: enclaveName, - dryRun: true, - baseDir: tmpDir, // Needs a valid base directory - templateFile: templatePath, - dataFile: dataPath, - buildDir: tmpDir, // Used by contract/prestate builders - urlBuilder: func(path ...string) string { - return "http://fileserver.test/" + strings.Join(path, "/") - }, - } - - buf, err := templater.Render(context.Background()) - require.NoError(t, err) - - // --- Assertions --- - output := buf.String() - t.Logf("Rendered output (dry run):\n%s", output) - - // 1. Verify template data is rendered - assert.Contains(t, output, "name: test-deployment") - - // 2. Verify Docker images are replaced with their *initial* tags (due to dryRun) - // and NOT the placeholder values. - expectedTagA := "project-a:" + enclaveName - expectedTagB := "project-b:" + enclaveName - assert.Contains(t, output, "imageA1: "+expectedTagA) - assert.Contains(t, output, "imageB: "+expectedTagB) - assert.Contains(t, output, "imageA2: "+expectedTagA) // Duplicate uses the same tag - assert.NotContains(t, output, "__PLACEHOLDER_DOCKER_IMAGE_") - - // 3. Verify contract artifacts URL is present (uses dry run logic of that builder) - assert.Contains(t, output, "contracts: artifact://contracts") - - // 4. Verify prestate hash placeholder is present (dry run for prestate needs specific setup) - // In dry run, the prestate builder might return zero values or specific placeholders. - // Based on `localPrestateHolder` implementation, it might error if files don't exist, - // or return default values. Let's assume it returns empty/default for dry run. - // Adjust this assertion based on the actual dry-run behavior of PrestateBuilder. - // For now, let's check if the key exists, assuming the dry run might produce an empty hash. - assert.Contains(t, output, "prestateHash:") // Check if the key is rendered - - // 5. Check that buildJobs map was populated (indirectly verifying first pass) - templater.buildJobsMux.Lock() - assert.Contains(t, templater.buildJobs, "project-a") - assert.Contains(t, templater.buildJobs, "project-b") - assert.Len(t, templater.buildJobs, 2, "Should only have jobs for unique project names") - templater.buildJobsMux.Unlock() -} - -func TestLocalPrestateOption(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "prestate-test") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - // Create a test build directory - buildDir := filepath.Join(tmpDir, "build") - require.NoError(t, os.MkdirAll(buildDir, 0755)) - - // Create a Templater instance - templater := &Templater{ - enclave: "test-enclave", - dryRun: true, - baseDir: tmpDir, - buildDir: buildDir, - urlBuilder: func(path ...string) string { - return "http://localhost:8080/" + strings.Join(path, "/") - }, - } - buildWg := &sync.WaitGroup{} - - // Get the localPrestate option - option := templater.localPrestateOption(context.Background(), buildWg) - - // Create a template context with the option - ctx := tmpl.NewTemplateContext(option) - - // Test the localPrestate function - localPrestateFn, ok := ctx.Functions["localPrestate"].(func() (*PrestateInfo, error)) - require.True(t, ok) - - prestate, err := localPrestateFn() - require.NoError(t, err) - - // Wait for the async goroutine to complete - buildWg.Wait() - - // In dry run mode, we should get a placeholder prestate with the correct URL - expectedURL := "http://localhost:8080/proofs/op-program/cannon" - assert.Equal(t, expectedURL, prestate.URL) - assert.Equal(t, "dry_run_placeholder", prestate.Hashes["prestate_mt64"]) - assert.Equal(t, "dry_run_placeholder", prestate.Hashes["prestate_interop"]) - - // Call it again to test caching - prestate2, err := localPrestateFn() - require.NoError(t, err) - assert.Equal(t, prestate, prestate2) -} - -func TestLocalContractArtifactsOption(t *testing.T) { - // Create a temporary directory for test files - tmpDir, err := os.MkdirTemp("", "contract-test") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - // Create the contracts directory structure - contractsDir := filepath.Join(tmpDir, "packages", "contracts-bedrock") - require.NoError(t, os.MkdirAll(contractsDir, 0755)) - - // Create a mock solidity cache file - cacheDir := filepath.Join(contractsDir, "cache") - require.NoError(t, os.MkdirAll(cacheDir, 0755)) - require.NoError(t, os.WriteFile(filepath.Join(cacheDir, "solidity-files-cache.json"), []byte("test cache"), 0644)) - - // Create a mock enclave manager - mockEnclaveManager := &enclave.KurtosisEnclaveManager{} - - // Create a Templater instance - templater := &Templater{ - enclave: "test-enclave", - dryRun: true, - baseDir: tmpDir, - enclaveManager: mockEnclaveManager, - urlBuilder: func(path ...string) string { - return "http://localhost:8080/" + strings.Join(path, "/") - }, - } - buildWg := &sync.WaitGroup{} - // Get the localContractArtifacts option - option := templater.localContractArtifactsOption(context.Background(), buildWg) - - // Create a template context with the option - ctx := tmpl.NewTemplateContext(option) - - // Test the localContractArtifacts function - localContractArtifactsFn, ok := ctx.Functions["localContractArtifacts"].(func(string) (string, error)) - require.True(t, ok) - - // Test with L1 layer - artifacts, err := localContractArtifactsFn("l1") - require.NoError(t, err) - assert.Equal(t, "artifact://contracts", artifacts) - - // Test with L2 layer - artifacts, err = localContractArtifactsFn("l2") - require.NoError(t, err) - assert.Equal(t, "artifact://contracts", artifacts) -} - -func TestRenderTemplate_PlainYamlFile(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "template-test") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - plainYamlContent := `optimism_package: - faucet: - enabled: true - chains: - chain1: - participants: - node1: - el: - type: op-geth - cl: - type: op-node - network_params: - network: "kurtosis" - network_id: "2151908" - interop_time_offset: 100 - chain2: - participants: - node1: - el: - type: op-geth - cl: - type: op-node - network_params: - network: "kurtosis" - network_id: "2151909" - interop_time_offset: 5000 -` - - templatePath := filepath.Join(tmpDir, "plain.yaml") - err = os.WriteFile(templatePath, []byte(plainYamlContent), 0644) - require.NoError(t, err) - - templater := &Templater{ - enclave: "test-enclave", - dryRun: true, - baseDir: tmpDir, - templateFile: templatePath, - buildDir: tmpDir, - urlBuilder: func(path ...string) string { - return "http://localhost:8080/" + strings.Join(path, "/") - }, - } - - buf, err := templater.Render(context.Background()) - require.NoError(t, err) - - // The output should be exactly the same as the input (no template processing) - assert.Equal(t, plainYamlContent, buf.String()) -} - -func TestRenderTemplate_PlainYamlWithDataFile(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "template-test-with-data") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - plainYamlContent := `optimism_package: - chains: - test-chain: - network_params: - network_id: "123456" -` - - templatePath := filepath.Join(tmpDir, "plain.yaml") - err = os.WriteFile(templatePath, []byte(plainYamlContent), 0644) - require.NoError(t, err) - - // Create a data file (even though the template doesn't use it) - dataContent := `{"someData": "value"}` - dataPath := filepath.Join(tmpDir, "data.json") - err = os.WriteFile(dataPath, []byte(dataContent), 0644) - require.NoError(t, err) - - templater := &Templater{ - enclave: "test-enclave", - dryRun: true, - baseDir: tmpDir, - templateFile: templatePath, - dataFile: dataPath, // Data file is irrelevant for plain YAML - buildDir: tmpDir, - urlBuilder: func(path ...string) string { - return "http://localhost:8080/" + strings.Join(path, "/") - }, - } - - buf, err := templater.Render(context.Background()) - require.NoError(t, err) - assert.Equal(t, plainYamlContent, buf.String()) -} - -func TestRenderTemplate_TemplateWithoutDataFile(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "template-test") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - // Create a file that DOES contain template syntax - templateContent := `optimism_package: - chains: - {{.chainName}}: - network_params: - network_id: "{{.networkId}}" -` - - templatePath := filepath.Join(tmpDir, "template.yaml") - err = os.WriteFile(templatePath, []byte(templateContent), 0644) - require.NoError(t, err) - - templater := &Templater{ - enclave: "test-enclave", - dryRun: true, - baseDir: tmpDir, - templateFile: templatePath, - dataFile: "", - buildDir: tmpDir, - urlBuilder: func(path ...string) string { - return "http://localhost:8080/" + strings.Join(path, "/") - }, - } - - // This should fail because the template has syntax but no data - _, err = templater.Render(context.Background()) - assert.Error(t, err, "Should fail when template has syntax but no data is provided") - assert.Contains(t, err.Error(), "failed to execute template") -} - -func TestRenderTemplate_EmptyFile(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "template-test") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - // Create an empty file - templatePath := filepath.Join(tmpDir, "empty.yaml") - err = os.WriteFile(templatePath, []byte(""), 0644) - require.NoError(t, err) - - templater := &Templater{ - enclave: "test", - dryRun: true, - baseDir: tmpDir, - templateFile: templatePath, - buildDir: tmpDir, - urlBuilder: func(...string) string { return "http://localhost" }, - } - - _, err = templater.Render(context.Background()) - assert.Error(t, err) - assert.Contains(t, err.Error(), "template file is empty") -} - -func TestRenderTemplate_FileDoesNotExist(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "template-test") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - nonExistentPath := filepath.Join(tmpDir, "nonexistent.yaml") - - templater := &Templater{ - enclave: "test", - dryRun: true, - baseDir: tmpDir, - templateFile: nonExistentPath, - buildDir: tmpDir, - urlBuilder: func(...string) string { return "http://localhost" }, - } - - _, err = templater.Render(context.Background()) - assert.Error(t, err) - assert.Contains(t, err.Error(), "template file does not exist") -} diff --git a/kurtosis-devnet/pkg/kurtosis/adapters.go b/kurtosis-devnet/pkg/kurtosis/adapters.go deleted file mode 100644 index 7bb3254b20e28..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/adapters.go +++ /dev/null @@ -1,54 +0,0 @@ -package kurtosis - -import ( - "context" - "io" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/deployer" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/depset" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/inspect" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/interfaces" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/jwt" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" -) - -type enclaveSpecAdapter struct{} - -func (a *enclaveSpecAdapter) EnclaveSpec(r io.Reader) (*spec.EnclaveSpec, error) { - return spec.NewSpec().ExtractData(r) -} - -var _ interfaces.EnclaveSpecifier = (*enclaveSpecAdapter)(nil) - -type enclaveInspectAdapter struct{} - -func (a *enclaveInspectAdapter) EnclaveInspect(ctx context.Context, enclave string) (*inspect.InspectData, error) { - return inspect.NewInspector(enclave).ExtractData(ctx) -} - -var _ interfaces.EnclaveInspecter = (*enclaveInspectAdapter)(nil) - -type enclaveDeployerAdapter struct{} - -func (a *enclaveDeployerAdapter) EnclaveObserve(ctx context.Context, enclave string) (*deployer.DeployerData, error) { - return deployer.NewDeployer(enclave).ExtractData(ctx) -} - -var _ interfaces.EnclaveObserver = (*enclaveDeployerAdapter)(nil) - -type enclaveJWTAdapter struct{} - -func (a *enclaveJWTAdapter) ExtractData(ctx context.Context, enclave string) (*jwt.Data, error) { - return jwt.NewExtractor(enclave).ExtractData(ctx) -} - -var _ interfaces.JWTExtractor = (*enclaveJWTAdapter)(nil) - -type enclaveDepsetAdapter struct{} - -func (a *enclaveDepsetAdapter) ExtractData(ctx context.Context, enclave string) (map[string]descriptors.DepSet, error) { - return depset.NewExtractor(enclave).ExtractData(ctx) -} - -var _ interfaces.DepsetExtractor = (*enclaveDepsetAdapter)(nil) diff --git a/kurtosis-devnet/pkg/kurtosis/api/enclave/enclave.go b/kurtosis-devnet/pkg/kurtosis/api/enclave/enclave.go deleted file mode 100644 index 92df86a43b3c8..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/api/enclave/enclave.go +++ /dev/null @@ -1,170 +0,0 @@ -package enclave - -import ( - "context" - "fmt" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/wrappers" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/util" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/trace" -) - -// DockerManager defines the interface for Docker operations -type DockerManager interface { - DestroyDockerResources(ctx context.Context, enclave ...string) error -} - -// DefaultDockerManager implements DockerManager using the util package -type DefaultDockerManager struct{} - -func (d *DefaultDockerManager) DestroyDockerResources(ctx context.Context, enclave ...string) error { - return util.DestroyDockerResources(ctx, enclave...) -} - -type KurtosisEnclaveManager struct { - kurtosisCtx interfaces.KurtosisContextInterface - dockerMgr DockerManager - tracer trace.Tracer -} - -type KurtosisEnclaveManagerOptions func(*KurtosisEnclaveManager) - -func WithKurtosisContext(kurtosisCtx interfaces.KurtosisContextInterface) KurtosisEnclaveManagerOptions { - return func(manager *KurtosisEnclaveManager) { - manager.kurtosisCtx = kurtosisCtx - } -} - -func WithDockerManager(dockerMgr DockerManager) KurtosisEnclaveManagerOptions { - return func(manager *KurtosisEnclaveManager) { - manager.dockerMgr = dockerMgr - } -} - -func NewKurtosisEnclaveManager(opts ...KurtosisEnclaveManagerOptions) (*KurtosisEnclaveManager, error) { - manager := &KurtosisEnclaveManager{ - tracer: otel.Tracer("enclave-manager"), - } - - for _, opt := range opts { - opt(manager) - } - - if manager.kurtosisCtx == nil { - var err error - manager.kurtosisCtx, err = wrappers.GetDefaultKurtosisContext() - if err != nil { - return nil, fmt.Errorf("failed to create Kurtosis context: %w", err) - } - } - return manager, nil -} - -func (mgr *KurtosisEnclaveManager) GetEnclave(ctx context.Context, enclave string) (interfaces.EnclaveContext, error) { - ctx, span := mgr.tracer.Start(ctx, "get enclave") - defer span.End() - - // Try to get existing enclave first - enclaveCtx, err := mgr.kurtosisCtx.GetEnclave(ctx, enclave) - if err != nil { - // If enclave doesn't exist, create a new one - fmt.Printf("Creating a new enclave for Starlark to run inside...\n") - enclaveCtx, err = mgr.kurtosisCtx.CreateEnclave(ctx, enclave) - if err != nil { - return nil, fmt.Errorf("failed to create enclave: %w", err) - } - fmt.Printf("Enclave '%s' created successfully\n\n", enclave) - } else { - fmt.Printf("Using existing enclave '%s'\n\n", enclave) - } - - return enclaveCtx, nil -} - -// cleanupEnclave handles the common cleanup logic for both stopped and empty enclaves -func (mgr *KurtosisEnclaveManager) cleanupEnclave(ctx context.Context, enclave string) error { - ctx, span := mgr.tracer.Start(ctx, "cleanup enclave") - defer span.End() - - // Remove the enclave - err := mgr.kurtosisCtx.DestroyEnclave(ctx, enclave) - if err != nil { - fmt.Printf("failed to destroy enclave: %v", err) - } else { - fmt.Printf("Destroyed enclave: %s\n", enclave) - } - var errDocker error - if mgr.dockerMgr != nil { - errDocker = mgr.dockerMgr.DestroyDockerResources(ctx, enclave) - if errDocker != nil { - fmt.Printf("failed to destroy docker resources: %v", errDocker) - } else { - fmt.Printf("Destroyed docker resources for enclave: %s\n", enclave) - } - } - if err != nil { - return err - } - if errDocker != nil { - return errDocker - } - return nil -} - -func (mgr *KurtosisEnclaveManager) Autofix(ctx context.Context, enclave string) error { - ctx, span := mgr.tracer.Start(ctx, "autofix enclave") - defer span.End() - - fmt.Printf("Autofixing enclave '%s'\n", enclave) - status, err := mgr.kurtosisCtx.GetEnclaveStatus(ctx, enclave) - if err != nil { - // Means the enclave doesn't exist, so we're good - fmt.Printf("Enclave '%s' does not exist, skipping autofix\n", enclave) - return nil - } - switch status { - case interfaces.EnclaveStatusRunning: - fmt.Printf("Enclave '%s' is running, skipping autofix\n", enclave) - return nil - case interfaces.EnclaveStatusStopped: - fmt.Printf("Enclave '%s' is stopped, removing\n", enclave) - return mgr.cleanupEnclave(ctx, enclave) - case interfaces.EnclaveStatusEmpty: - fmt.Printf("Enclave '%s' is empty, removing\n", enclave) - return mgr.cleanupEnclave(ctx, enclave) - } - return fmt.Errorf("unknown enclave status: %s", status) -} - -func (mgr *KurtosisEnclaveManager) Nuke(ctx context.Context) error { - ctx, span := mgr.tracer.Start(ctx, "nuke enclaves") - defer span.End() - - enclaves, err := mgr.kurtosisCtx.Clean(ctx, true) - if err != nil { - fmt.Printf("failed to clean enclaves: %v", err) - } else { - fmt.Printf("Cleaned enclaves\n") - } - for _, enclave := range enclaves { - fmt.Printf("Nuked enclave: %s\n", enclave.GetName()) - } - var errDocker error - if mgr.dockerMgr != nil { - errDocker = mgr.dockerMgr.DestroyDockerResources(ctx) - if errDocker != nil { - fmt.Printf("failed to destroy docker resources: %v", errDocker) - } else { - fmt.Printf("Destroyed docker resources\n") - } - } - if err != nil { - return err - } - if errDocker != nil { - return errDocker - } - return nil -} diff --git a/kurtosis-devnet/pkg/kurtosis/api/enclave/enclave_test.go b/kurtosis-devnet/pkg/kurtosis/api/enclave/enclave_test.go deleted file mode 100644 index fe9f57e3c602a..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/api/enclave/enclave_test.go +++ /dev/null @@ -1,241 +0,0 @@ -package enclave - -import ( - "context" - "errors" - "testing" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/fake" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// MockDockerManager implements DockerManager for testing -type MockDockerManager struct{} - -func (m *MockDockerManager) DestroyDockerResources(ctx context.Context, enclave ...string) error { - return nil -} - -func TestNewKurtosisEnclaveManager(t *testing.T) { - tests := []struct { - name string - opts []KurtosisEnclaveManagerOptions - wantErr bool - }{ - { - name: "create with fake context", - opts: []KurtosisEnclaveManagerOptions{ - WithKurtosisContext(&fake.KurtosisContext{}), - }, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - manager, err := NewKurtosisEnclaveManager(tt.opts...) - if tt.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - assert.NotNil(t, manager) - }) - } -} - -func TestGetEnclave(t *testing.T) { - tests := []struct { - name string - enclave string - fakeCtx *fake.KurtosisContext - wantErr bool - wantCalls []string - }{ - { - name: "get existing enclave", - enclave: "test-enclave", - fakeCtx: &fake.KurtosisContext{ - EnclaveCtx: &fake.EnclaveContext{}, - }, - wantErr: false, - wantCalls: []string{"get"}, - }, - { - name: "create new enclave when not exists", - enclave: "test-enclave", - fakeCtx: &fake.KurtosisContext{ - GetErr: errors.New("enclave not found"), - EnclaveCtx: &fake.EnclaveContext{}, - }, - wantErr: false, - wantCalls: []string{"get", "create"}, - }, - { - name: "error on get and create", - enclave: "test-enclave", - fakeCtx: &fake.KurtosisContext{ - GetErr: errors.New("get error"), - CreateErr: errors.New("create error"), - }, - wantErr: true, - wantCalls: []string{"get", "create"}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - manager, err := NewKurtosisEnclaveManager( - WithKurtosisContext(tt.fakeCtx), - WithDockerManager(&MockDockerManager{}), - ) - require.NoError(t, err) - - ctx := context.Background() - enclaveCtx, err := manager.GetEnclave(ctx, tt.enclave) - - if tt.wantErr { - assert.Error(t, err) - return - } - - assert.NoError(t, err) - assert.NotNil(t, enclaveCtx) - }) - } -} - -func TestAutofix(t *testing.T) { - tests := []struct { - name string - enclave string - fakeCtx *fake.KurtosisContext - status interfaces.EnclaveStatus - statusErr error - destroyErr error - wantErr bool - wantDestroyed bool - }{ - { - name: "running enclave", - enclave: "test-enclave", - fakeCtx: &fake.KurtosisContext{}, - status: interfaces.EnclaveStatusRunning, - wantErr: false, - }, - { - name: "stopped enclave", - enclave: "test-enclave", - fakeCtx: &fake.KurtosisContext{}, - status: interfaces.EnclaveStatusStopped, - wantErr: false, - wantDestroyed: true, - }, - { - name: "empty enclave", - enclave: "test-enclave", - fakeCtx: &fake.KurtosisContext{}, - status: interfaces.EnclaveStatusEmpty, - wantErr: false, - wantDestroyed: true, - }, - { - name: "enclave not found", - enclave: "test-enclave", - fakeCtx: &fake.KurtosisContext{}, - statusErr: errors.New("enclave not found"), - wantErr: false, - }, - { - name: "destroy error", - enclave: "test-enclave", - fakeCtx: &fake.KurtosisContext{}, - status: interfaces.EnclaveStatusStopped, - destroyErr: errors.New("destroy error"), - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Setup mock context - tt.fakeCtx.Status = tt.status - tt.fakeCtx.StatusErr = tt.statusErr - tt.fakeCtx.DestroyErr = tt.destroyErr - - manager, err := NewKurtosisEnclaveManager( - WithKurtosisContext(tt.fakeCtx), - WithDockerManager(&MockDockerManager{}), - ) - require.NoError(t, err) - - ctx := context.Background() - err = manager.Autofix(ctx, tt.enclave) - - if tt.wantErr { - assert.Error(t, err) - return - } - - assert.NoError(t, err) - if tt.wantDestroyed { - assert.True(t, tt.fakeCtx.DestroyCalled, "Destroy should be called") - } else { - assert.False(t, tt.fakeCtx.DestroyCalled, "Destroy should not be called") - } - }) - } -} - -func TestNuke(t *testing.T) { - tests := []struct { - name string - fakeCtx *fake.KurtosisContext - cleanErr error - wantErr bool - wantClean bool - }{ - { - name: "successful nuke", - fakeCtx: &fake.KurtosisContext{}, - wantErr: false, - wantClean: true, - }, - { - name: "clean error", - fakeCtx: &fake.KurtosisContext{}, - cleanErr: errors.New("clean error"), - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Setup mock context - tt.fakeCtx.CleanErr = tt.cleanErr - - manager, err := NewKurtosisEnclaveManager( - WithKurtosisContext(tt.fakeCtx), - WithDockerManager(&MockDockerManager{}), - ) - require.NoError(t, err) - - ctx := context.Background() - err = manager.Nuke(ctx) - - if tt.wantErr { - assert.Error(t, err) - return - } - - assert.NoError(t, err) - if tt.wantClean { - assert.True(t, tt.fakeCtx.CleanCalled, "Clean should be called") - } else { - assert.False(t, tt.fakeCtx.CleanCalled, "Clean should not be called") - } - }) - } -} diff --git a/kurtosis-devnet/pkg/kurtosis/api/engine/engine.go b/kurtosis-devnet/pkg/kurtosis/api/engine/engine.go deleted file mode 100644 index 158c14647bc4e..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/api/engine/engine.go +++ /dev/null @@ -1,116 +0,0 @@ -package engine - -import ( - "fmt" - "os" - "os/exec" - "strings" - - "github.com/kurtosis-tech/kurtosis/api/golang/kurtosis_version" - "gopkg.in/yaml.v3" -) - -// EngineManager handles running the Kurtosis engine -type EngineManager struct { - kurtosisBinary string - version string -} - -// Option configures an EngineManager -type Option func(*EngineManager) - -// WithKurtosisBinary sets the path to the kurtosis binary -func WithKurtosisBinary(binary string) Option { - return func(e *EngineManager) { - e.kurtosisBinary = binary - } -} - -// WithVersion sets the engine version -func WithVersion(version string) Option { - return func(e *EngineManager) { - e.version = version - } -} - -// NewEngineManager creates a new EngineManager with the given options -func NewEngineManager(opts ...Option) *EngineManager { - e := &EngineManager{ - kurtosisBinary: "kurtosis", // Default to expecting kurtosis in PATH - version: kurtosis_version.KurtosisVersion, // Default to library version - } - for _, opt := range opts { - opt(e) - } - return e -} - -// EnsureRunning starts the Kurtosis engine with the configured version -func (e *EngineManager) EnsureRunning() error { - cmd := exec.Command(e.kurtosisBinary, "engine", "start", "--version", e.version) - fmt.Println("Starting Kurtosis engine with version:", e.version) - - // Capture stdout and stderr for more verbose output - var stdout, stderr strings.Builder - cmd.Stdout = &stdout - cmd.Stderr = &stderr - - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to start kurtosis engine: %w\nstdout: %s\nstderr: %s", - err, stdout.String(), stderr.String()) - } - return nil -} - -// GetEngineType gets the type of the running engine (docker, kubernetes, etc) -func (e *EngineManager) GetEngineType() (string, error) { - // First try to get the cluster name - cmd := exec.Command(e.kurtosisBinary, "cluster", "get") - output, err := cmd.Output() - if err != nil { - // Means there's no cluster set, so we're using the default cluster - // Which is the first entry in kurtosis cluster ls - cmd = exec.Command(e.kurtosisBinary, "cluster", "ls") - output, err = cmd.Output() - if err != nil { - return "", fmt.Errorf("failed to get cluster info: %w", err) - } - clusterName := strings.TrimSpace(string(output)) - return clusterName, nil - } - clusterName := strings.TrimSpace(string(output)) - - cmd = exec.Command(e.kurtosisBinary, "config", "path") - output, err = cmd.Output() - if err != nil { - return "", fmt.Errorf("failed to get config path: %w", err) - } - configPath := strings.TrimSpace(string(output)) - - configData, err := os.ReadFile(configPath) - if err != nil { - return "", fmt.Errorf("failed to read config file: %w", err) - } - - var config struct { - KurtosisClusters map[string]struct { - Type string `yaml:"type"` - } `yaml:"kurtosis-clusters"` - } - if err := yaml.Unmarshal(configData, &config); err != nil { - return "", fmt.Errorf("failed to parse config file: %w", err) - } - - cluster, exists := config.KurtosisClusters[clusterName] - if !exists { - // Means we're using the cluster definitions from the default config - return clusterName, nil - } - - return cluster.Type, nil -} - -func (e *EngineManager) RestartEngine() error { - cmd := exec.Command(e.kurtosisBinary, "engine", "restart") - return cmd.Run() -} diff --git a/kurtosis-devnet/pkg/kurtosis/api/engine/engine_test.go b/kurtosis-devnet/pkg/kurtosis/api/engine/engine_test.go deleted file mode 100644 index 93f5d367c6de0..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/api/engine/engine_test.go +++ /dev/null @@ -1,188 +0,0 @@ -package engine - -import ( - "os" - "path/filepath" - "testing" - - "github.com/kurtosis-tech/kurtosis/api/golang/kurtosis_version" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewEngineManager(t *testing.T) { - tests := []struct { - name string - opts []Option - expectedBinary string - expectedVer string - }{ - { - name: "default options", - opts: []Option{}, - expectedBinary: "kurtosis", - expectedVer: kurtosis_version.KurtosisVersion, - }, - { - name: "custom binary path", - opts: []Option{WithKurtosisBinary("/custom/path/kurtosis")}, - expectedBinary: "/custom/path/kurtosis", - expectedVer: kurtosis_version.KurtosisVersion, - }, - { - name: "custom version", - opts: []Option{WithVersion("1.0.0")}, - expectedBinary: "kurtosis", - expectedVer: "1.0.0", - }, - { - name: "custom binary and version", - opts: []Option{WithKurtosisBinary("/custom/path/kurtosis"), WithVersion("1.0.0")}, - expectedBinary: "/custom/path/kurtosis", - expectedVer: "1.0.0", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - manager := NewEngineManager(tt.opts...) - assert.Equal(t, tt.expectedBinary, manager.kurtosisBinary) - assert.Equal(t, tt.expectedVer, manager.version) - }) - } -} - -func TestEnsureRunning(t *testing.T) { - // Create a temporary directory for testing - tempDir, err := os.MkdirTemp("", "kurtosis-test-*") - require.NoError(t, err) - defer os.RemoveAll(tempDir) - - // Create a mock kurtosis binary that captures and verifies the arguments - mockBinary := filepath.Join(tempDir, "kurtosis") - mockScript := `#!/bin/sh -if [ "$1" = "engine" ] && [ "$2" = "start" ] && [ "$3" = "--version" ] && [ "$4" = "test-version" ]; then - echo "Engine started with version test-version" - exit 0 -else - echo "Invalid arguments: $@" - exit 1 -fi` - err = os.WriteFile(mockBinary, []byte(mockScript), 0755) - require.NoError(t, err) - - manager := NewEngineManager( - WithKurtosisBinary(mockBinary), - WithVersion("test-version"), - ) - err = manager.EnsureRunning() - assert.NoError(t, err) -} - -func TestGetEngineType(t *testing.T) { - // Create a temporary directory for testing - tempDir, err := os.MkdirTemp("", "kurtosis-test-*") - require.NoError(t, err) - defer os.RemoveAll(tempDir) - - // Create a mock kurtosis binary that simulates cluster commands - mockBinary := filepath.Join(tempDir, "kurtosis") - mockScript := `#!/bin/sh -if [ "$1" = "cluster" ] && [ "$2" = "get" ]; then - echo "test-cluster" -elif [ "$1" = "config" ] && [ "$2" = "path" ]; then - echo "` + tempDir + `/config.yaml" -else - exit 1 -fi` - err = os.WriteFile(mockBinary, []byte(mockScript), 0755) - require.NoError(t, err) - - // Create a mock config file - configPath := filepath.Join(tempDir, "config.yaml") - configContent := ` -kurtosis-clusters: - test-cluster: - type: docker -` - err = os.WriteFile(configPath, []byte(configContent), 0644) - require.NoError(t, err) - - manager := NewEngineManager(WithKurtosisBinary(mockBinary)) - engineType, err := manager.GetEngineType() - assert.NoError(t, err) - assert.Equal(t, "docker", engineType) -} - -func TestGetEngineType_NoCluster(t *testing.T) { - // Create a temporary directory for testing - tempDir, err := os.MkdirTemp("", "kurtosis-test-*") - require.NoError(t, err) - defer os.RemoveAll(tempDir) - - // Create a mock kurtosis binary that simulates no cluster set - mockBinary := filepath.Join(tempDir, "kurtosis") - mockScript := `#!/bin/sh -if [ "$1" = "cluster" ] && [ "$2" = "get" ]; then - exit 1 -elif [ "$1" = "cluster" ] && [ "$2" = "ls" ]; then - echo "default-cluster" -else - exit 1 -fi` - err = os.WriteFile(mockBinary, []byte(mockScript), 0755) - require.NoError(t, err) - - manager := NewEngineManager(WithKurtosisBinary(mockBinary)) - engineType, err := manager.GetEngineType() - assert.NoError(t, err) - assert.Equal(t, "default-cluster", engineType) -} - -func TestRestartEngine(t *testing.T) { - // Create a temporary directory for testing - tempDir, err := os.MkdirTemp("", "kurtosis-test-*") - require.NoError(t, err) - defer os.RemoveAll(tempDir) - - // Create a mock kurtosis binary that captures and verifies the arguments - mockBinary := filepath.Join(tempDir, "kurtosis") - mockScript := `#!/bin/sh -if [ "$1" = "engine" ] && [ "$2" = "restart" ]; then - echo "Engine restarted successfully" - exit 0 -else - echo "Invalid arguments: $@" - exit 1 -fi` - err = os.WriteFile(mockBinary, []byte(mockScript), 0755) - require.NoError(t, err) - - manager := NewEngineManager(WithKurtosisBinary(mockBinary)) - err = manager.RestartEngine() - assert.NoError(t, err) -} - -func TestRestartEngine_Failure(t *testing.T) { - // Create a temporary directory for testing - tempDir, err := os.MkdirTemp("", "kurtosis-test-*") - require.NoError(t, err) - defer os.RemoveAll(tempDir) - - // Create a mock kurtosis binary that always fails - mockBinary := filepath.Join(tempDir, "kurtosis") - mockScript := `#!/bin/sh -if [ "$1" = "engine" ] && [ "$2" = "restart" ]; then - echo "Failed to restart engine" - exit 1 -else - echo "Invalid arguments: $@" - exit 1 -fi` - err = os.WriteFile(mockBinary, []byte(mockScript), 0755) - require.NoError(t, err) - - manager := NewEngineManager(WithKurtosisBinary(mockBinary)) - err = manager.RestartEngine() - assert.Error(t, err) -} diff --git a/kurtosis-devnet/pkg/kurtosis/api/fake/fake.go b/kurtosis-devnet/pkg/kurtosis/api/fake/fake.go deleted file mode 100644 index a9800f806d9bd..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/api/fake/fake.go +++ /dev/null @@ -1,289 +0,0 @@ -package fake - -import ( - "context" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/enclaves" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/starlark_run_config" -) - -// KurtosisContext implements interfaces.KurtosisContextInterface for testing -type KurtosisContext struct { - EnclaveCtx *EnclaveContext - GetErr error - CreateErr error - CleanErr error - DestroyErr error - Status interfaces.EnclaveStatus - StatusErr error - DestroyCalled bool - CleanCalled bool -} - -func (f *KurtosisContext) CreateEnclave(ctx context.Context, name string) (interfaces.EnclaveContext, error) { - if f.CreateErr != nil { - return nil, f.CreateErr - } - return f.EnclaveCtx, nil -} - -func (f *KurtosisContext) GetEnclave(ctx context.Context, name string) (interfaces.EnclaveContext, error) { - if f.GetErr != nil { - return nil, f.GetErr - } - return f.EnclaveCtx, nil -} - -func (f *KurtosisContext) GetEnclaveStatus(ctx context.Context, name string) (interfaces.EnclaveStatus, error) { - if f.StatusErr != nil { - return "", f.StatusErr - } - return f.Status, nil -} - -func (f *KurtosisContext) Clean(ctx context.Context, destroyAll bool) ([]interfaces.EnclaveNameAndUuid, error) { - f.CleanCalled = true - if f.CleanErr != nil { - return nil, f.CleanErr - } - return []interfaces.EnclaveNameAndUuid{}, nil -} - -func (f *KurtosisContext) DestroyEnclave(ctx context.Context, name string) error { - f.DestroyCalled = true - if f.DestroyErr != nil { - return f.DestroyErr - } - return nil -} - -// EnclaveContext implements interfaces.EnclaveContext for testing -type EnclaveContext struct { - RunErr error - Responses []interfaces.StarlarkResponse - ArtifactNames []*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid - ArtifactData []byte - UploadErr error - - Services map[services.ServiceName]interfaces.ServiceContext - Files map[services.FileArtifactName]string -} - -func (f *EnclaveContext) GetEnclaveUuid() enclaves.EnclaveUUID { - return enclaves.EnclaveUUID("test-enclave-uuid") -} - -func (f *EnclaveContext) GetServices() (map[services.ServiceName]services.ServiceUUID, error) { - if f.Services == nil { - return nil, nil - } - services := make(map[services.ServiceName]services.ServiceUUID) - for name, svc := range f.Services { - services[name] = svc.GetServiceUUID() - } - return services, nil -} - -func (f *EnclaveContext) GetService(serviceIdentifier string) (interfaces.ServiceContext, error) { - if f.Services == nil { - return nil, nil - } - return f.Services[services.ServiceName(serviceIdentifier)], nil -} - -func (f *EnclaveContext) GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) { - artifacts := make([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, 0, len(f.Files)) - for name, uuid := range f.Files { - artifacts = append(artifacts, &kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid{ - FileName: string(name), - FileUuid: string(uuid), - }) - } - return artifacts, nil -} - -func (f *EnclaveContext) RunStarlarkPackage(ctx context.Context, pkg string, params *starlark_run_config.StarlarkRunConfig) (<-chan interfaces.StarlarkResponse, string, error) { - if f.RunErr != nil { - return nil, "", f.RunErr - } - - // Create a channel and send all responses - ch := make(chan interfaces.StarlarkResponse) - go func() { - defer close(ch) - for _, resp := range f.Responses { - ch <- resp - } - }() - return ch, "", nil -} - -func (f *EnclaveContext) RunStarlarkScript(ctx context.Context, script string, params *starlark_run_config.StarlarkRunConfig) error { - return f.RunErr -} - -func (f *EnclaveContext) DownloadFilesArtifact(ctx context.Context, name string) ([]byte, error) { - return f.ArtifactData, nil -} - -func (f *EnclaveContext) UploadFiles(pathToUpload string, artifactName string) (services.FilesArtifactUUID, services.FileArtifactName, error) { - if f.UploadErr != nil { - return "", "", f.UploadErr - } - return "test-uuid", services.FileArtifactName(artifactName), nil -} - -type ServiceContext struct { - ServiceUUID services.ServiceUUID - PublicIP string - PrivateIP string - PublicPorts map[string]*services.PortSpec - PrivatePorts map[string]*services.PortSpec -} - -func (f *ServiceContext) GetServiceUUID() services.ServiceUUID { - return f.ServiceUUID -} - -func (f *ServiceContext) GetMaybePublicIPAddress() string { - return f.PublicIP -} - -func (f *ServiceContext) GetPublicPorts() map[string]*services.PortSpec { - return f.PublicPorts -} - -func (f *ServiceContext) GetPrivatePorts() map[string]*services.PortSpec { - return f.PrivatePorts -} - -// StarlarkResponse implements interfaces.StarlarkResponse for testing -type StarlarkResponse struct { - Err interfaces.StarlarkError - ProgressMsg []string - Instruction string - IsSuccessful bool - Warning string - Info string - Result string - HasResult bool // tracks whether result was explicitly set -} - -func (f *StarlarkResponse) GetError() interfaces.StarlarkError { - return f.Err -} - -func (f *StarlarkResponse) GetProgressInfo() interfaces.ProgressInfo { - if f.ProgressMsg != nil { - return &ProgressInfo{Info: f.ProgressMsg} - } - return nil -} - -func (f *StarlarkResponse) GetInstruction() interfaces.Instruction { - if f.Instruction != "" { - return &Instruction{Desc: f.Instruction} - } - return nil -} - -func (f *StarlarkResponse) GetRunFinishedEvent() interfaces.RunFinishedEvent { - return &RunFinishedEvent{IsSuccessful: f.IsSuccessful} -} - -func (f *StarlarkResponse) GetWarning() interfaces.Warning { - if f.Warning != "" { - return &Warning{Msg: f.Warning} - } - return nil -} - -func (f *StarlarkResponse) GetInfo() interfaces.Info { - if f.Info != "" { - return &Info{Msg: f.Info} - } - return nil -} - -func (f *StarlarkResponse) GetInstructionResult() interfaces.InstructionResult { - if !f.HasResult { - return nil - } - return &InstructionResult{Result: f.Result} -} - -// ProgressInfo implements ProgressInfo for testing -type ProgressInfo struct { - Info []string -} - -func (f *ProgressInfo) GetCurrentStepInfo() []string { - return f.Info -} - -// Instruction implements Instruction for testing -type Instruction struct { - Desc string -} - -func (f *Instruction) GetDescription() string { - return f.Desc -} - -// StarlarkError implements StarlarkError for testing -type StarlarkError struct { - InterpretationErr error - ValidationErr error - ExecutionErr error -} - -func (f *StarlarkError) GetInterpretationError() error { - return f.InterpretationErr -} - -func (f *StarlarkError) GetValidationError() error { - return f.ValidationErr -} - -func (f *StarlarkError) GetExecutionError() error { - return f.ExecutionErr -} - -// RunFinishedEvent implements RunFinishedEvent for testing -type RunFinishedEvent struct { - IsSuccessful bool -} - -func (f *RunFinishedEvent) GetIsRunSuccessful() bool { - return f.IsSuccessful -} - -// Warning implements Warning for testing -type Warning struct { - Msg string -} - -func (f *Warning) GetMessage() string { - return f.Msg -} - -// Info implements Info for testing -type Info struct { - Msg string -} - -func (f *Info) GetMessage() string { - return f.Msg -} - -// InstructionResult implements InstructionResult for testing -type InstructionResult struct { - Result string -} - -func (f *InstructionResult) GetSerializedInstructionResult() string { - return f.Result -} diff --git a/kurtosis-devnet/pkg/kurtosis/api/interfaces/iface.go b/kurtosis-devnet/pkg/kurtosis/api/interfaces/iface.go deleted file mode 100644 index 0b3b532bda2fa..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/api/interfaces/iface.go +++ /dev/null @@ -1,96 +0,0 @@ -package interfaces - -import ( - "context" - - "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/enclaves" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/starlark_run_config" -) - -// Interfaces for Kurtosis SDK types to make testing easier -type StarlarkError interface { - GetInterpretationError() error - GetValidationError() error - GetExecutionError() error -} - -type ProgressInfo interface { - GetCurrentStepInfo() []string -} - -type Instruction interface { - GetDescription() string -} - -type RunFinishedEvent interface { - GetIsRunSuccessful() bool -} - -type Warning interface { - GetMessage() string -} - -type Info interface { - GetMessage() string -} - -type InstructionResult interface { - GetSerializedInstructionResult() string -} - -type StarlarkResponse interface { - GetError() StarlarkError - GetProgressInfo() ProgressInfo - GetInstruction() Instruction - GetRunFinishedEvent() RunFinishedEvent - GetWarning() Warning - GetInfo() Info - GetInstructionResult() InstructionResult -} - -type PortSpec interface { - GetNumber() uint16 -} - -type ServiceContext interface { - GetServiceUUID() services.ServiceUUID - GetMaybePublicIPAddress() string - GetPublicPorts() map[string]PortSpec - GetPrivatePorts() map[string]PortSpec - GetLabels() map[string]string -} - -type EnclaveContext interface { - GetEnclaveUuid() enclaves.EnclaveUUID - - GetService(serviceIdentifier string) (ServiceContext, error) - GetServices() (map[services.ServiceName]services.ServiceUUID, error) - - GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) - - RunStarlarkPackage(context.Context, string, *starlark_run_config.StarlarkRunConfig) (<-chan StarlarkResponse, string, error) - RunStarlarkScript(context.Context, string, *starlark_run_config.StarlarkRunConfig) error -} - -type EnclaveNameAndUuid interface { - GetName() string - GetUuid() string -} - -type EnclaveStatus string - -const ( - EnclaveStatusEmpty EnclaveStatus = "empty" - EnclaveStatusRunning EnclaveStatus = "running" - EnclaveStatusStopped EnclaveStatus = "stopped" -) - -type KurtosisContextInterface interface { - CreateEnclave(context.Context, string) (EnclaveContext, error) - GetEnclave(context.Context, string) (EnclaveContext, error) - GetEnclaveStatus(context.Context, string) (EnclaveStatus, error) - DestroyEnclave(context.Context, string) error - Clean(context.Context, bool) ([]EnclaveNameAndUuid, error) -} diff --git a/kurtosis-devnet/pkg/kurtosis/api/run/handlers.go b/kurtosis-devnet/pkg/kurtosis/api/run/handlers.go deleted file mode 100644 index 3c4c719063e57..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/api/run/handlers.go +++ /dev/null @@ -1,174 +0,0 @@ -package run - -import ( - "context" - "fmt" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/fatih/color" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/trace" -) - -// Color printers -var ( - printCyan = color.New(color.FgCyan).SprintFunc() - printYellow = color.New(color.FgYellow).SprintFunc() - printRed = color.New(color.FgRed).SprintFunc() - printBlue = color.New(color.FgBlue).SprintFunc() -) - -// MessageHandler defines the interface for handling different types of messages -type MessageHandler interface { - // Handle processes the message if applicable and returns: - // - bool: whether the message was handled - // - error: any error that occurred during handling - Handle(context.Context, interfaces.StarlarkResponse) (bool, error) -} - -// MessageHandlerFunc is a function type that implements MessageHandler -type MessageHandlerFunc func(context.Context, interfaces.StarlarkResponse) (bool, error) - -func (f MessageHandlerFunc) Handle(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { - return f(ctx, resp) -} - -// FirstMatchHandler returns a handler that applies the first matching handler from the given handlers -func FirstMatchHandler(handlers ...MessageHandler) MessageHandler { - return MessageHandlerFunc(func(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { - for _, h := range handlers { - handled, err := h.Handle(ctx, resp) - if err != nil { - return true, err - } - if handled { - return true, nil - } - } - return false, nil - }) -} - -// AllHandlers returns a handler that applies all the given handlers in order -func AllHandlers(handlers ...MessageHandler) MessageHandler { - return MessageHandlerFunc(func(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { - anyHandled := false - for _, h := range handlers { - handled, err := h.Handle(ctx, resp) - if err != nil { - return true, err - } - anyHandled = anyHandled || handled - } - return anyHandled, nil - }) -} - -// defaultHandler is the default message handler that provides standard Kurtosis output -type defaultHandler struct { - tracer trace.Tracer - span trace.Span -} - -func newDefaultHandler() *defaultHandler { - return &defaultHandler{ - tracer: otel.Tracer("kurtosis-run"), - } -} - -var _ MessageHandler = (*defaultHandler)(nil) - -func (h *defaultHandler) Handle(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { - hdlr := FirstMatchHandler( - MessageHandlerFunc(h.handleProgress), - MessageHandlerFunc(h.handleInstruction), - MessageHandlerFunc(h.handleWarning), - MessageHandlerFunc(h.handleInfo), - MessageHandlerFunc(h.handleResult), - MessageHandlerFunc(h.handleError), - ) - - return hdlr.Handle(ctx, resp) -} - -// handleProgress handles progress info messages -func (h *defaultHandler) handleProgress(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { - if progressInfo := resp.GetProgressInfo(); progressInfo != nil { - // ignore progress messages, same as kurtosis run does - return true, nil - } - return false, nil -} - -// handleInstruction handles instruction messages -func (h *defaultHandler) handleInstruction(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { - if instruction := resp.GetInstruction(); instruction != nil { - desc := instruction.GetDescription() - _, span := h.tracer.Start(ctx, desc) - h.span = span - - fmt.Println(printCyan(desc)) - return true, nil - } - return false, nil -} - -// handleWarning handles warning messages -func (h *defaultHandler) handleWarning(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { - if warning := resp.GetWarning(); warning != nil { - fmt.Println(printYellow(warning.GetMessage())) - return true, nil - } - return false, nil -} - -// handleInfo handles info messages -func (h *defaultHandler) handleInfo(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { - if info := resp.GetInfo(); info != nil { - fmt.Println(printBlue(info.GetMessage())) - return true, nil - } - return false, nil -} - -// handleResult handles instruction result messages -func (h *defaultHandler) handleResult(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { - if result := resp.GetInstructionResult(); result != nil { - if result.GetSerializedInstructionResult() != "" { - fmt.Printf("%s\n\n", result.GetSerializedInstructionResult()) - } - if h.span != nil { - h.span.End() - } - return true, nil - } - return false, nil -} - -// handleError handles error messages -func (h *defaultHandler) handleError(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { - if err := resp.GetError(); err != nil { - if interpretErr := err.GetInterpretationError(); interpretErr != nil { - return true, fmt.Errorf(printRed("interpretation error: %v"), interpretErr) - } - if validationErr := err.GetValidationError(); validationErr != nil { - return true, fmt.Errorf(printRed("validation error: %v"), validationErr) - } - if executionErr := err.GetExecutionError(); executionErr != nil { - return true, fmt.Errorf(printRed("execution error: %v"), executionErr) - } - return true, nil - } - return false, nil -} - -// makeRunFinishedHandler creates a handler for run finished events -func makeRunFinishedHandler(isSuccessful *bool) MessageHandlerFunc { - return func(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { - if event := resp.GetRunFinishedEvent(); event != nil { - *isSuccessful = event.GetIsRunSuccessful() - return true, nil - } - return false, nil - } -} diff --git a/kurtosis-devnet/pkg/kurtosis/api/run/handlers_test.go b/kurtosis-devnet/pkg/kurtosis/api/run/handlers_test.go deleted file mode 100644 index 580642d921061..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/api/run/handlers_test.go +++ /dev/null @@ -1,374 +0,0 @@ -package run - -import ( - "context" - "fmt" - "testing" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/fake" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/stretchr/testify/assert" -) - -func TestHandleProgress(t *testing.T) { - ctx := context.Background() - d := newDefaultHandler() - tests := []struct { - name string - response interfaces.StarlarkResponse - want bool - }{ - { - name: "handles progress message", - response: &fake.StarlarkResponse{ - ProgressMsg: []string{"Step 1", "Step 2"}, - }, - want: true, - }, - { - name: "ignores non-progress message", - response: &fake.StarlarkResponse{}, - want: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - handled, err := d.handleProgress(ctx, tt.response) - assert.NoError(t, err) - assert.Equal(t, tt.want, handled) - }) - } -} - -func TestHandleInstruction(t *testing.T) { - ctx := context.Background() - d := newDefaultHandler() - tests := []struct { - name string - response interfaces.StarlarkResponse - want bool - }{ - { - name: "handles instruction message", - response: &fake.StarlarkResponse{ - Instruction: "Execute command", - }, - want: true, - }, - { - name: "ignores non-instruction message", - response: &fake.StarlarkResponse{}, - want: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - handled, err := d.handleInstruction(ctx, tt.response) - assert.NoError(t, err) - assert.Equal(t, tt.want, handled) - }) - } -} - -func TestHandleWarning(t *testing.T) { - ctx := context.Background() - d := newDefaultHandler() - tests := []struct { - name string - response interfaces.StarlarkResponse - want bool - }{ - { - name: "handles warning message", - response: &fake.StarlarkResponse{ - Warning: "Warning: deprecated feature", - }, - want: true, - }, - { - name: "ignores non-warning message", - response: &fake.StarlarkResponse{}, - want: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - handled, err := d.handleWarning(ctx, tt.response) - assert.NoError(t, err) - assert.Equal(t, tt.want, handled) - }) - } -} - -func TestHandleInfo(t *testing.T) { - ctx := context.Background() - d := newDefaultHandler() - tests := []struct { - name string - response interfaces.StarlarkResponse - want bool - }{ - { - name: "handles info message", - response: &fake.StarlarkResponse{ - Info: "System info", - }, - want: true, - }, - { - name: "ignores non-info message", - response: &fake.StarlarkResponse{}, - want: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - handled, err := d.handleInfo(ctx, tt.response) - assert.NoError(t, err) - assert.Equal(t, tt.want, handled) - }) - } -} - -func TestHandleResult(t *testing.T) { - ctx := context.Background() - d := newDefaultHandler() - tests := []struct { - name string - response interfaces.StarlarkResponse - want bool - }{ - { - name: "handles result message", - response: &fake.StarlarkResponse{ - Result: "Operation completed", - HasResult: true, - }, - want: true, - }, - { - name: "handles empty result message", - response: &fake.StarlarkResponse{ - Result: "", - HasResult: true, - }, - want: true, - }, - { - name: "ignores non-result message", - response: &fake.StarlarkResponse{}, - want: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - handled, err := d.handleResult(ctx, tt.response) - assert.NoError(t, err) - assert.Equal(t, tt.want, handled) - }) - } -} - -func TestHandleError(t *testing.T) { - ctx := context.Background() - d := newDefaultHandler() - testErr := fmt.Errorf("test error") - tests := []struct { - name string - response interfaces.StarlarkResponse - want bool - wantError bool - }{ - { - name: "handles interpretation error", - response: &fake.StarlarkResponse{ - Err: &fake.StarlarkError{InterpretationErr: testErr}, - }, - want: true, - wantError: true, - }, - { - name: "handles validation error", - response: &fake.StarlarkResponse{ - Err: &fake.StarlarkError{ValidationErr: testErr}, - }, - want: true, - wantError: true, - }, - { - name: "handles execution error", - response: &fake.StarlarkResponse{ - Err: &fake.StarlarkError{ExecutionErr: testErr}, - }, - want: true, - wantError: true, - }, - { - name: "ignores non-error message", - response: &fake.StarlarkResponse{}, - want: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - handled, err := d.handleError(ctx, tt.response) - if tt.wantError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - assert.Equal(t, tt.want, handled) - }) - } -} - -func TestFirstMatchHandler(t *testing.T) { - ctx := context.Background() - d := newDefaultHandler() - testErr := fmt.Errorf("test error") - tests := []struct { - name string - handlers []MessageHandler - response interfaces.StarlarkResponse - want bool - wantError bool - }{ - { - name: "first handler matches", - handlers: []MessageHandler{ - MessageHandlerFunc(d.handleInfo), - MessageHandlerFunc(d.handleWarning), - }, - response: &fake.StarlarkResponse{ - Info: "test info", - }, - want: true, - }, - { - name: "second handler matches", - handlers: []MessageHandler{ - MessageHandlerFunc(d.handleInfo), - MessageHandlerFunc(d.handleWarning), - }, - response: &fake.StarlarkResponse{ - Warning: "test warning", - }, - want: true, - }, - { - name: "no handlers match", - handlers: []MessageHandler{ - MessageHandlerFunc(d.handleInfo), - MessageHandlerFunc(d.handleWarning), - }, - response: &fake.StarlarkResponse{ - Result: "test result", HasResult: true, - }, - want: false, - }, - { - name: "handler returns error", - handlers: []MessageHandler{ - MessageHandlerFunc(d.handleError), - }, - response: &fake.StarlarkResponse{ - Err: &fake.StarlarkError{InterpretationErr: testErr}, - }, - want: true, - wantError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - handler := FirstMatchHandler(tt.handlers...) - handled, err := handler.Handle(ctx, tt.response) - if tt.wantError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - assert.Equal(t, tt.want, handled) - }) - } -} - -func TestAllHandlers(t *testing.T) { - ctx := context.Background() - d := newDefaultHandler() - testErr := fmt.Errorf("test error") - tests := []struct { - name string - handlers []MessageHandler - response interfaces.StarlarkResponse - want bool - wantError bool - }{ - { - name: "multiple handlers match", - handlers: []MessageHandler{ - MessageHandlerFunc(func(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { - return true, nil - }), - MessageHandlerFunc(func(ctx context.Context, resp interfaces.StarlarkResponse) (bool, error) { - return true, nil - }), - }, - response: &fake.StarlarkResponse{}, - want: true, - }, - { - name: "some handlers match", - handlers: []MessageHandler{ - MessageHandlerFunc(d.handleInfo), - MessageHandlerFunc(d.handleWarning), - }, - response: &fake.StarlarkResponse{ - Info: "test info", - }, - want: true, - }, - { - name: "no handlers match", - handlers: []MessageHandler{ - MessageHandlerFunc(d.handleInfo), - MessageHandlerFunc(d.handleWarning), - }, - response: &fake.StarlarkResponse{ - Result: "test result", HasResult: true, - }, - want: false, - }, - { - name: "handler returns error", - handlers: []MessageHandler{ - MessageHandlerFunc(d.handleInfo), - MessageHandlerFunc(d.handleError), - }, - response: &fake.StarlarkResponse{ - Err: &fake.StarlarkError{InterpretationErr: testErr}, - }, - want: true, - wantError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - handler := AllHandlers(tt.handlers...) - handled, err := handler.Handle(ctx, tt.response) - if tt.wantError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - assert.Equal(t, tt.want, handled) - }) - } -} diff --git a/kurtosis-devnet/pkg/kurtosis/api/run/kurtosis_run.go b/kurtosis-devnet/pkg/kurtosis/api/run/kurtosis_run.go deleted file mode 100644 index bd317e9b6f7ad..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/api/run/kurtosis_run.go +++ /dev/null @@ -1,154 +0,0 @@ -package run - -import ( - "context" - "errors" - "fmt" - "io" - "os" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/enclave" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/wrappers" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/starlark_run_config" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/trace" -) - -type KurtosisRunner struct { - dryRun bool - enclave string - kurtosisCtx interfaces.KurtosisContextInterface - runHandlers []MessageHandler - tracer trace.Tracer -} - -type KurtosisRunnerOptions func(*KurtosisRunner) - -func WithKurtosisRunnerDryRun(dryRun bool) KurtosisRunnerOptions { - return func(r *KurtosisRunner) { - r.dryRun = dryRun - } -} - -func WithKurtosisRunnerEnclave(enclave string) KurtosisRunnerOptions { - return func(r *KurtosisRunner) { - r.enclave = enclave - } -} - -func WithKurtosisRunnerKurtosisContext(kurtosisCtx interfaces.KurtosisContextInterface) KurtosisRunnerOptions { - return func(r *KurtosisRunner) { - r.kurtosisCtx = kurtosisCtx - } -} - -func WithKurtosisRunnerRunHandlers(runHandlers ...MessageHandler) KurtosisRunnerOptions { - return func(r *KurtosisRunner) { - r.runHandlers = runHandlers - } -} - -func NewKurtosisRunner(opts ...KurtosisRunnerOptions) (*KurtosisRunner, error) { - r := &KurtosisRunner{ - tracer: otel.Tracer("kurtosis-run"), - } - for _, opt := range opts { - opt(r) - } - - if r.kurtosisCtx == nil { - var err error - r.kurtosisCtx, err = wrappers.GetDefaultKurtosisContext() - if err != nil { - return nil, fmt.Errorf("failed to create Kurtosis context: %w", err) - } - } - return r, nil -} - -func (r *KurtosisRunner) Run(ctx context.Context, packageName string, args io.Reader) error { - ctx, span := r.tracer.Start(ctx, fmt.Sprintf("run package %s", packageName)) - defer span.End() - - if r.dryRun { - fmt.Printf("Dry run mode enabled, would run kurtosis package %s in enclave %s\n", - packageName, r.enclave) - if args != nil { - fmt.Println("\nWith arguments:") - if _, err := io.Copy(os.Stdout, args); err != nil { - return fmt.Errorf("failed to dump args: %w", err) - } - fmt.Println() - } - return nil - } - - mgr, err := enclave.NewKurtosisEnclaveManager( - enclave.WithKurtosisContext(r.kurtosisCtx), - ) - if err != nil { - return fmt.Errorf("failed to create Kurtosis enclave manager: %w", err) - } - // Try to get existing enclave first - enclaveCtx, err := mgr.GetEnclave(ctx, r.enclave) - if err != nil { - return fmt.Errorf("failed to get enclave: %w", err) - } - - // Set up run config with args if provided - serializedParams := "{}" - if args != nil { - argsBytes, err := io.ReadAll(args) - if err != nil { - return fmt.Errorf("failed to read args: %w", err) - } - serializedParams = string(argsBytes) - } - - runConfig := &starlark_run_config.StarlarkRunConfig{ - SerializedParams: serializedParams, - } - - stream, _, err := enclaveCtx.RunStarlarkPackage(ctx, packageName, runConfig) - if err != nil { - return fmt.Errorf("failed to run Kurtosis package: %w", err) - } - - // Set up message handlers - var isRunSuccessful bool - runFinishedHandler := makeRunFinishedHandler(&isRunSuccessful) - - // Combine custom handlers with default handler and run finished handler - handler := AllHandlers(append(r.runHandlers, newDefaultHandler(), runFinishedHandler)...) - - // Process the output stream - for responseLine := range stream { - if _, err := handler.Handle(ctx, responseLine); err != nil { - return err - } - } - - if !isRunSuccessful { - return errors.New(printRed("kurtosis package execution failed")) - } - - return nil -} - -func (r *KurtosisRunner) RunScript(ctx context.Context, script string) error { - if r.dryRun { - fmt.Printf("Dry run mode enabled, would run following script in enclave %s\n%s\n", - r.enclave, script) - return nil - } - - enclaveCtx, err := r.kurtosisCtx.GetEnclave(ctx, r.enclave) - if err != nil { - return fmt.Errorf("failed to get enclave: %w", err) - } - - return enclaveCtx.RunStarlarkScript(ctx, script, &starlark_run_config.StarlarkRunConfig{ - SerializedParams: "{}", - }) -} diff --git a/kurtosis-devnet/pkg/kurtosis/api/run/kurtosis_run_test.go b/kurtosis-devnet/pkg/kurtosis/api/run/kurtosis_run_test.go deleted file mode 100644 index dd50d929941cd..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/api/run/kurtosis_run_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package run - -import ( - "context" - "fmt" - "testing" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/fake" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestRunKurtosis(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - testErr := fmt.Errorf("test error") - tests := []struct { - name string - responses []fake.StarlarkResponse - kurtosisErr error - getErr error - wantErr bool - }{ - { - name: "successful run with all message types", - responses: []fake.StarlarkResponse{ - {ProgressMsg: []string{"Starting deployment..."}}, - {Info: "Preparing environment"}, - {Instruction: "Executing package"}, - {Warning: "Using default config"}, - {Result: "Service started", HasResult: true}, - {ProgressMsg: []string{"Deployment complete"}}, - {IsSuccessful: true}, - }, - wantErr: false, - }, - { - name: "run with error", - responses: []fake.StarlarkResponse{ - {ProgressMsg: []string{"Starting deployment..."}}, - {Err: &fake.StarlarkError{ExecutionErr: testErr}}, - }, - wantErr: true, - }, - { - name: "run with unsuccessful completion", - responses: []fake.StarlarkResponse{ - {ProgressMsg: []string{"Starting deployment..."}}, - {IsSuccessful: false}, - }, - wantErr: true, - }, - { - name: "kurtosis error", - kurtosisErr: fmt.Errorf("kurtosis failed"), - wantErr: true, - }, - { - name: "uses existing enclave", - responses: []fake.StarlarkResponse{ - {ProgressMsg: []string{"Using existing enclave"}}, - {IsSuccessful: true}, - }, - getErr: nil, - wantErr: false, - }, - { - name: "creates new enclave when get fails", - responses: []fake.StarlarkResponse{ - {ProgressMsg: []string{"Creating new enclave"}}, - {IsSuccessful: true}, - }, - getErr: fmt.Errorf("enclave not found"), - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Convert test responses to interface slice - interfaceResponses := make([]interfaces.StarlarkResponse, len(tt.responses)) - for i := range tt.responses { - interfaceResponses[i] = &tt.responses[i] - } - - // Create a fake enclave context that will return our test responses - fakeCtx := &fake.KurtosisContext{ - EnclaveCtx: &fake.EnclaveContext{ - RunErr: tt.kurtosisErr, - Responses: interfaceResponses, - }, - GetErr: tt.getErr, - } - - kurtosisRunner, err := NewKurtosisRunner( - WithKurtosisRunnerDryRun(false), - WithKurtosisRunnerEnclave("test-enclave"), - WithKurtosisRunnerKurtosisContext(fakeCtx), - ) - require.NoError(t, err) - - err = kurtosisRunner.Run(ctx, "test-package", nil) - if tt.wantErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} diff --git a/kurtosis-devnet/pkg/kurtosis/api/wrappers/wrappers.go b/kurtosis-devnet/pkg/kurtosis/api/wrappers/wrappers.go deleted file mode 100644 index db3172fac6bbe..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/api/wrappers/wrappers.go +++ /dev/null @@ -1,269 +0,0 @@ -package wrappers - -import ( - "context" - "errors" - "fmt" - "strings" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/enclaves" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/starlark_run_config" - "github.com/kurtosis-tech/kurtosis/api/golang/engine/kurtosis_engine_rpc_api_bindings" - "github.com/kurtosis-tech/kurtosis/api/golang/engine/lib/kurtosis_context" -) - -// Wrapper types to implement our interfaces -type KurtosisContextWrapper struct { - *kurtosis_context.KurtosisContext -} - -type EnclaveContextWrapper struct { - *enclaves.EnclaveContext -} - -type ServiceContextWrapper struct { - *services.ServiceContext -} - -type EnclaveInfoWrapper struct { - *kurtosis_engine_rpc_api_bindings.EnclaveInfo -} - -// mostly a no-op, to force the values to be typed as interfaces -func convertPortSpecMap(ports map[string]*services.PortSpec) map[string]interfaces.PortSpec { - wrappedPorts := make(map[string]interfaces.PortSpec) - for name, port := range ports { - wrappedPorts[name] = port - } - return wrappedPorts -} - -func (w *ServiceContextWrapper) GetPublicPorts() map[string]interfaces.PortSpec { - return convertPortSpecMap(w.ServiceContext.GetPublicPorts()) -} - -func (w *ServiceContextWrapper) GetPrivatePorts() map[string]interfaces.PortSpec { - return convertPortSpecMap(w.ServiceContext.GetPrivatePorts()) -} - -type starlarkRunResponseLineWrapper struct { - *kurtosis_core_rpc_api_bindings.StarlarkRunResponseLine -} - -type starlarkErrorWrapper struct { - *kurtosis_core_rpc_api_bindings.StarlarkError -} - -type starlarkRunProgressWrapper struct { - *kurtosis_core_rpc_api_bindings.StarlarkRunProgress -} - -type starlarkInstructionWrapper struct { - *kurtosis_core_rpc_api_bindings.StarlarkInstruction -} - -type starlarkRunFinishedEventWrapper struct { - *kurtosis_core_rpc_api_bindings.StarlarkRunFinishedEvent -} - -type starlarkWarningWrapper struct { - *kurtosis_core_rpc_api_bindings.StarlarkWarning -} - -type starlarkInfoWrapper struct { - *kurtosis_core_rpc_api_bindings.StarlarkInfo -} - -type starlarkInstructionResultWrapper struct { - *kurtosis_core_rpc_api_bindings.StarlarkInstructionResult -} - -type EnclaveNameAndUuidWrapper struct { - *kurtosis_engine_rpc_api_bindings.EnclaveNameAndUuid -} - -func (w KurtosisContextWrapper) CreateEnclave(ctx context.Context, name string) (interfaces.EnclaveContext, error) { - enclaveCtx, err := w.KurtosisContext.CreateEnclave(ctx, name) - if err != nil { - return nil, err - } - return &EnclaveContextWrapper{enclaveCtx}, nil -} - -func (w KurtosisContextWrapper) GetEnclave(ctx context.Context, name string) (interfaces.EnclaveContext, error) { - enclaveCtx, err := w.KurtosisContext.GetEnclaveContext(ctx, name) - if err != nil { - return nil, err - } - return &EnclaveContextWrapper{enclaveCtx}, nil -} - -func (w *EnclaveContextWrapper) GetService(serviceIdentifier string) (interfaces.ServiceContext, error) { - svcCtx, err := w.EnclaveContext.GetServiceContext(serviceIdentifier) - if err != nil { - return nil, err - } - return &ServiceContextWrapper{svcCtx}, nil -} - -func (w KurtosisContextWrapper) GetEnclaveStatus(ctx context.Context, enclave string) (interfaces.EnclaveStatus, error) { - enclaveInfo, err := w.KurtosisContext.GetEnclave(ctx, enclave) - if err != nil { - return "", err - } - status := enclaveInfo.GetContainersStatus() - switch status { - case kurtosis_engine_rpc_api_bindings.EnclaveContainersStatus_EnclaveContainersStatus_EMPTY: - return interfaces.EnclaveStatusEmpty, nil - case kurtosis_engine_rpc_api_bindings.EnclaveContainersStatus_EnclaveContainersStatus_RUNNING: - return interfaces.EnclaveStatusRunning, nil - case kurtosis_engine_rpc_api_bindings.EnclaveContainersStatus_EnclaveContainersStatus_STOPPED: - return interfaces.EnclaveStatusStopped, nil - default: - return "", fmt.Errorf("unknown enclave status: %v", status) - } -} - -func (w KurtosisContextWrapper) DestroyEnclave(ctx context.Context, name string) error { - return w.KurtosisContext.DestroyEnclave(ctx, name) -} - -func (w KurtosisContextWrapper) Clean(ctx context.Context, destroyAll bool) ([]interfaces.EnclaveNameAndUuid, error) { - deleted, err := w.KurtosisContext.Clean(ctx, destroyAll) - if err != nil { - return nil, err - } - - result := make([]interfaces.EnclaveNameAndUuid, len(deleted)) - for i, nameAndUuid := range deleted { - result[i] = &EnclaveNameAndUuidWrapper{nameAndUuid} - } - return result, nil -} - -func (w *EnclaveContextWrapper) RunStarlarkPackage(ctx context.Context, pkg string, serializedParams *starlark_run_config.StarlarkRunConfig) (<-chan interfaces.StarlarkResponse, string, error) { - runner := w.EnclaveContext.RunStarlarkPackage - if strings.HasPrefix(pkg, "github.com/") { - runner = w.EnclaveContext.RunStarlarkRemotePackage - } - - stream, cancel, err := runner(ctx, pkg, serializedParams) - if err != nil { - return nil, "", err - } - - // Convert the stream - wrappedStream := make(chan interfaces.StarlarkResponse) - go func() { - defer close(wrappedStream) - defer cancel() - for line := range stream { - wrappedStream <- &starlarkRunResponseLineWrapper{line} - } - }() - - return wrappedStream, "", nil -} - -func (w *EnclaveContextWrapper) RunStarlarkScript(ctx context.Context, script string, serializedParams *starlark_run_config.StarlarkRunConfig) error { - // TODO: we should probably collect some data from the result and extend the error. - _, err := w.EnclaveContext.RunStarlarkScriptBlocking(ctx, script, serializedParams) - return err -} - -func (w *starlarkRunResponseLineWrapper) GetError() interfaces.StarlarkError { - if err := w.StarlarkRunResponseLine.GetError(); err != nil { - return &starlarkErrorWrapper{err} - } - return nil -} - -func (w *starlarkRunResponseLineWrapper) GetProgressInfo() interfaces.ProgressInfo { - if progress := w.StarlarkRunResponseLine.GetProgressInfo(); progress != nil { - return &starlarkRunProgressWrapper{progress} - } - return nil -} - -func (w *starlarkRunResponseLineWrapper) GetInstruction() interfaces.Instruction { - if instruction := w.StarlarkRunResponseLine.GetInstruction(); instruction != nil { - return &starlarkInstructionWrapper{instruction} - } - return nil -} - -func (w *starlarkRunResponseLineWrapper) GetRunFinishedEvent() interfaces.RunFinishedEvent { - if event := w.StarlarkRunResponseLine.GetRunFinishedEvent(); event != nil { - return &starlarkRunFinishedEventWrapper{event} - } - return nil -} - -func (w *starlarkRunResponseLineWrapper) GetWarning() interfaces.Warning { - if warning := w.StarlarkRunResponseLine.GetWarning(); warning != nil { - return &starlarkWarningWrapper{warning} - } - return nil -} - -func (w *starlarkRunResponseLineWrapper) GetInfo() interfaces.Info { - if info := w.StarlarkRunResponseLine.GetInfo(); info != nil { - return &starlarkInfoWrapper{info} - } - return nil -} - -func (w *starlarkRunResponseLineWrapper) GetInstructionResult() interfaces.InstructionResult { - if result := w.StarlarkRunResponseLine.GetInstructionResult(); result != nil { - return &starlarkInstructionResultWrapper{result} - } - return nil -} - -func (w *starlarkRunProgressWrapper) GetCurrentStepInfo() []string { - return w.StarlarkRunProgress.CurrentStepInfo -} - -func (w *starlarkInstructionWrapper) GetDescription() string { - return w.StarlarkInstruction.Description -} - -func (w *starlarkRunFinishedEventWrapper) GetIsRunSuccessful() bool { - return w.StarlarkRunFinishedEvent.IsRunSuccessful -} - -func (w *starlarkErrorWrapper) GetInterpretationError() error { - if err := w.StarlarkError.GetInterpretationError(); err != nil { - return errors.New(err.GetErrorMessage()) - } - return nil -} - -func (w *starlarkErrorWrapper) GetValidationError() error { - if err := w.StarlarkError.GetValidationError(); err != nil { - return errors.New(err.GetErrorMessage()) - } - return nil -} - -func (w *starlarkErrorWrapper) GetExecutionError() error { - if err := w.StarlarkError.GetExecutionError(); err != nil { - return errors.New(err.GetErrorMessage()) - } - return nil -} - -func (w *starlarkWarningWrapper) GetMessage() string { - return w.StarlarkWarning.WarningMessage -} - -func (w *starlarkInfoWrapper) GetMessage() string { - return w.StarlarkInfo.InfoMessage -} - -func (w *starlarkInstructionResultWrapper) GetSerializedInstructionResult() string { - return w.StarlarkInstructionResult.SerializedInstructionResult -} diff --git a/kurtosis-devnet/pkg/kurtosis/api/wrappers/wrappers_local.go b/kurtosis-devnet/pkg/kurtosis/api/wrappers/wrappers_local.go deleted file mode 100644 index 3e984df8d22f5..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/api/wrappers/wrappers_local.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build !testonly -// +build !testonly - -package wrappers - -import ( - "fmt" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/kurtosis-tech/kurtosis/api/golang/engine/lib/kurtosis_context" -) - -func GetDefaultKurtosisContext() (interfaces.KurtosisContextInterface, error) { - kCtx, err := kurtosis_context.NewKurtosisContextFromLocalEngine() - if err != nil { - return nil, fmt.Errorf("failed to create Kurtosis context: %w", err) - } - return KurtosisContextWrapper{ - KurtosisContext: kCtx, - }, nil -} diff --git a/kurtosis-devnet/pkg/kurtosis/api/wrappers/wrappers_testing.go b/kurtosis-devnet/pkg/kurtosis/api/wrappers/wrappers_testing.go deleted file mode 100644 index af2c9a7510294..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/api/wrappers/wrappers_testing.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build testonly -// +build testonly - -package wrappers - -import ( - "errors" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" -) - -func GetDefaultKurtosisContext() (interfaces.KurtosisContextInterface, error) { - return nil, errors.New("attempting to use local Kurtosis context in testonly mode") -} diff --git a/kurtosis-devnet/pkg/kurtosis/endpoints.go b/kurtosis-devnet/pkg/kurtosis/endpoints.go deleted file mode 100644 index ad786e42f800d..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/endpoints.go +++ /dev/null @@ -1,384 +0,0 @@ -package kurtosis - -import ( - "encoding/json" - "strconv" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/inspect" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" -) - -// ServiceFinder is the main entry point for finding services and their endpoints -type ServiceFinder struct { - services inspect.ServiceMap - - l1Chain *spec.ChainSpec - l2Chains []*spec.ChainSpec - depsets map[string]descriptors.DepSet - - triagedServices []*triagedService -} - -// ServiceFinderOption configures a ServiceFinder -type ServiceFinderOption func(*ServiceFinder) - -// WithL1Chain sets the L1 chain -func WithL1Chain(chain *spec.ChainSpec) ServiceFinderOption { - return func(f *ServiceFinder) { - f.l1Chain = chain - } -} - -// WithL2Chains sets the L2 networks -func WithL2Chains(networks []*spec.ChainSpec) ServiceFinderOption { - return func(f *ServiceFinder) { - f.l2Chains = networks - } -} - -// WithDepSets sets the dependency sets -func WithDepSets(depsets map[string]descriptors.DepSet) ServiceFinderOption { - return func(f *ServiceFinder) { - f.depsets = depsets - } -} - -// NewServiceFinder creates a new ServiceFinder with the given options -func NewServiceFinder(services inspect.ServiceMap, opts ...ServiceFinderOption) *ServiceFinder { - f := &ServiceFinder{ - services: services, - } - for _, opt := range opts { - opt(f) - } - - f.triage() - return f -} - -type chainAcceptor func(*spec.ChainSpec) bool - -type serviceParser func(string) (int, chainAcceptor, bool) - -type triagedService struct { - tag string // service tag - idx int // service index (for nodes) - name string // service name (for nodes) - svc *descriptors.Service - accept chainAcceptor -} - -func acceptAll(c *spec.ChainSpec) bool { - return true -} - -func acceptID(s string) chainAcceptor { - return func(c *spec.ChainSpec) bool { - return c.NetworkID == s - } -} - -func acceptIDs(ids ...string) chainAcceptor { - acceptors := make([]chainAcceptor, 0) - for _, id := range ids { - acceptors = append(acceptors, acceptID(id)) - } - return combineAcceptors(acceptors...) -} - -func combineAcceptors(acceptors ...chainAcceptor) chainAcceptor { - return func(c *spec.ChainSpec) bool { - for _, acceptor := range acceptors { - if acceptor(c) { - return true - } - } - return false - } -} - -// This is now for L1 only. L2 is handled through labels. -func (f *ServiceFinder) triageNode(prefix string) serviceParser { - return func(serviceName string) (int, chainAcceptor, bool) { - extractIndex := func(s string) int { - // Extract numeric index from service name - parts := strings.Split(s, "-") - if idx, err := strconv.ParseUint(parts[0], 10, 32); err == nil { - return int(idx) - 1 - } - return 0 - } - - if strings.HasPrefix(serviceName, prefix) { // L1 - idx := extractIndex(strings.TrimPrefix(serviceName, prefix)) - return idx, acceptID(f.l1Chain.NetworkID), true - } - - return 0, nil, false - } -} - -type serviceParserRules map[string]serviceParser - -func (spr serviceParserRules) apply(serviceName string, endpoints descriptors.EndpointMap) *triagedService { - for tag, rule := range spr { - if idx, accept, ok := rule(serviceName); ok { - return &triagedService{ - tag: tag, - idx: idx, - accept: accept, - svc: &descriptors.Service{ - Name: serviceName, - Endpoints: endpoints, - }, - } - } - } - return nil -} - -// TODO: this might need some adjustments as we stabilize labels in optimism-package -const ( - kindLabel = "op.kind" - networkIDLabel = "op.network.id" - nodeNameLabel = "op.network.participant.name" - nodeIndexLabel = "op.network.participant.index" - supervisorSuperchainLabel = "op.network.supervisor.superchain" -) - -func (f *ServiceFinder) getNetworkIDs(svc *inspect.Service) []string { - var network_ids []string - id, ok := svc.Labels[networkIDLabel] - if !ok { - // network IDs might be specified through a superchain - superchain, ok := svc.Labels[supervisorSuperchainLabel] - if !ok { - return nil - } - ds, ok := f.depsets[superchain] - if !ok { - return nil - } - var depSet depset.StaticConfigDependencySet - err := json.Unmarshal(ds, &depSet) - if err != nil { - return nil - } - for _, chain := range depSet.Chains() { - network_ids = append(network_ids, chain.String()) - } - } else { - network_ids = strings.Split(id, "-") - } - - return network_ids -} - -func (f *ServiceFinder) triageByLabels(svc *inspect.Service, name string, endpoints descriptors.EndpointMap) *triagedService { - tag, ok := svc.Labels[kindLabel] - if !ok { - return nil - } - - // So that we can have the same behaviour as netchef - if (tag == "flashblocks-websocket-proxy") && endpoints != nil { - if _, has := endpoints["ws-flashblocks"]; !has { - if ws, ok := endpoints["ws"]; ok { - endpoints["ws-flashblocks"] = ws - } - } - } - network_ids := f.getNetworkIDs(svc) - idx := -1 - if val, ok := svc.Labels[nodeIndexLabel]; ok { - i, err := strconv.Atoi(val) - if err != nil { - return nil - } - idx = i - } - - accept := acceptIDs(network_ids...) - if len(network_ids) == 0 { // TODO: this is only for faucet right now, we can remove this once we have a proper label for all services - accept = acceptAll - } - return &triagedService{ - tag: tag, - idx: idx, - name: svc.Labels[nodeNameLabel], - accept: accept, - svc: &descriptors.Service{ - Name: name, - Endpoints: endpoints, - }, - } -} - -func (f *ServiceFinder) triage() { - rules := serviceParserRules{ - "el": f.triageNode("el-"), - "cl": f.triageNode("cl-"), - } - - triagedServices := []*triagedService{} - for serviceName, svc := range f.services { - endpoints := make(descriptors.EndpointMap) - for portName, portInfo := range svc.Ports { - endpoints[portName] = portInfo - } - - // Ultimately we'll rely only on labels, and most of the code in this file will disappear as a result. - // - // For now though the L1 services are still not tagged properly so we rely on the name resolution as a fallback - triaged := f.triageByLabels(svc, serviceName, endpoints) - if triaged == nil { - triaged = rules.apply(serviceName, endpoints) - } - - if triaged != nil { - triagedServices = append(triagedServices, triaged) - } - } - - f.triagedServices = triagedServices -} - -func (f *ServiceFinder) findChainServices(chain *spec.ChainSpec) ([]descriptors.Node, descriptors.RedundantServiceMap) { - var nodes []descriptors.Node - services := make(descriptors.RedundantServiceMap) - - var selected []*triagedService - for _, svc := range f.triagedServices { - if svc.accept(chain) { - if svc.idx >= len(nodes) { - // just resize the slice, that'll create "0" items for the new indices. - // We don't expect more than a few nodes per chain, so this is fine. - nodes = make([]descriptors.Node, svc.idx+1) - } - if svc.idx < 0 { // not a node service - // create a dummy entry for the service - services[svc.tag] = nil - } - selected = append(selected, svc) - } - } - - // Now our slice is the right size, and our map has the right keys, we can just fill in the data - for _, svc := range selected { - if svc.idx >= 0 { - node := nodes[svc.idx] - if node.Services == nil { - node.Services = make(descriptors.ServiceMap) - } - node.Services[svc.tag] = svc.svc - node.Name = svc.name - - if cfg, ok := chain.Nodes[node.Name]; ok { - node.Labels = make(map[string]string) - if cfg.IsSequencer { - node.Labels["sequencer"] = "true" - } - node.Labels["elType"] = cfg.ELType - node.Labels["clType"] = cfg.CLType - } - - nodes[svc.idx] = node - } else { - services[svc.tag] = append(services[svc.tag], svc.svc) - } - } - - return reorderNodes(nodes), services -} - -// FindL1Services finds L1 nodes. -func (f *ServiceFinder) FindL1Services() ([]descriptors.Node, descriptors.RedundantServiceMap) { - return f.findChainServices(f.l1Chain) -} - -// FindL2Services finds L2 nodes and services for a specific network -func (f *ServiceFinder) FindL2Services(s *spec.ChainSpec) ([]descriptors.Node, descriptors.RedundantServiceMap) { - return f.findChainServices(s) -} - -// TODO: remove this once we remove the devnet-sdk/system test framework. -// At that point the order of the nodes will not be important anymore. -func reorderNodes(nodes []descriptors.Node) []descriptors.Node { - // This is a hack to preserve some compatibililty with prior expectations, - // that were embedded in the devnet-sdk/system test framework. - // - // We need to rearrange the order of the nodes so that: - // - either there are nodes in the list that contain a label "sequencer", - // and then one of them must be the first node - // - or there are no nodes with the label "sequencer", and there are some - // with el type "op-geth" and cl type "op-node". Then one of them must be - // the first node - // - or none of the above, and then we keep the order as is - - if len(nodes) == 0 { - return nodes - } - - // First, check if any node has the "sequencer" label - var sequencerIndex int = -1 - for i, node := range nodes { - if node.Labels != nil && node.Labels["sequencer"] == "true" { - sequencerIndex = i - break - } - } - - // If we found a sequencer, move it to the front - if sequencerIndex >= 0 { - return moveNodeToFront(nodes, sequencerIndex) - } - - // If no sequencer found, look for nodes with el type "op-geth" and cl type "op-node" - var opGethOpNodeIndex int = -1 - for i, node := range nodes { - if node.Services != nil { - hasOpGeth := false - hasOpNode := false - - // Check for op-geth service - if node.Labels != nil && node.Labels["elType"] == "op-geth" { - hasOpGeth = true - } - - // Check for op-node service - if node.Labels != nil && node.Labels["clType"] == "op-node" { - hasOpNode = true - } - - if hasOpGeth && hasOpNode { - opGethOpNodeIndex = i - break - } - } - } - - // If we found a node with both op-geth and op-node, move it to the front - if opGethOpNodeIndex >= 0 { - return moveNodeToFront(nodes, opGethOpNodeIndex) - } - - // If none of the above conditions are met, return the nodes in their original order - return nodes -} - -func moveNodeToFront(nodes []descriptors.Node, index int) []descriptors.Node { - if index < 0 || index >= len(nodes) { - return nodes - } - - result := make([]descriptors.Node, len(nodes)) - copy(result, nodes) - // Move the node at the specified index to the front - nodeToMove := result[index] - copy(result[1:index+1], result[:index]) - result[0] = nodeToMove - return result -} diff --git a/kurtosis-devnet/pkg/kurtosis/endpoints_test.go b/kurtosis-devnet/pkg/kurtosis/endpoints_test.go deleted file mode 100644 index d46444cc5586f..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/endpoints_test.go +++ /dev/null @@ -1,465 +0,0 @@ -package kurtosis - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/inspect" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestFindChainServices(t *testing.T) { - // Create test chains based on the scenario - chain1 := &spec.ChainSpec{ - Name: "op-kurtosis-1", - NetworkID: "2151908", - } - chain2 := &spec.ChainSpec{ - Name: "op-kurtosis-2", - NetworkID: "2151909", - } - chains := []*spec.ChainSpec{chain1, chain2} - - // Create mock dependency set - depSets := createTestDepSets(t) - - // Create mock service map based on inspect data from the scenario - services := createTestServiceMap() - - // Create service finder with the test data - finder := NewServiceFinder( - services, - WithL1Chain(&spec.ChainSpec{NetworkID: "0"}), - WithL2Chains(chains), - WithDepSets(depSets), - ) - - // Test triage directly to ensure services are correctly triaged - t.Run("triage services", func(t *testing.T) { - assert.NotNil(t, finder.triagedServices, "Triaged services should not be nil") - assert.NotEmpty(t, finder.triagedServices, "Triaged services should not be empty") - - // Count service types - tagCount := make(map[string]int) - for _, svc := range finder.triagedServices { - tagCount[svc.tag]++ - } - - // Verify expected service counts - assert.Equal(t, 3, tagCount["cl"], "Should have 3 CL services") - assert.Equal(t, 3, tagCount["el"], "Should have 3 EL service") - assert.Equal(t, 2, tagCount["batcher"], "Should have 2 batcher services") - assert.Equal(t, 2, tagCount["proposer"], "Should have 2 proposer services") - assert.Equal(t, 2, tagCount["proxyd"], "Should have 2 proxyd services") - assert.Equal(t, 1, tagCount["challenger"], "Should have 1 challenger service") - assert.Equal(t, 1, tagCount["supervisor"], "Should have 1 supervisor service") - assert.Equal(t, 1, tagCount["faucet"], "Should have 1 faucet service") - }) - - // Test L1 service discovery - t.Run("L1 services", func(t *testing.T) { - nodes, services := finder.FindL1Services() - - // Verify L1 nodes - assert.Equal(t, 1, len(nodes), "Should have exactly 1 node") - - // Verify L1 services - assert.Equal(t, 1, len(services), "Should have exactly 1 service") - assert.Contains(t, services, "faucet", "Should have faucet service") - }) - - // Test L2 services for both chains - for _, chain := range chains { - t.Run(fmt.Sprintf("L2 %s services", chain.Name), func(t *testing.T) { - nodes, services := finder.FindL2Services(chain) - - assert.Equal(t, 1, len(nodes), "Should have exactly 1 node") - assert.Equal(t, 6, len(services), "Should have exactly 6 services") - - assert.Contains(t, services, "batcher", "Should have batcher service") - assert.Contains(t, services, "proposer", "Should have proposer service") - assert.Contains(t, services, "proxyd", "Should have proxyd service") - assert.Contains(t, services, "challenger", "Should have challenger service") - assert.Contains(t, services, "supervisor", "Should have supervisor service") - assert.Contains(t, services, "faucet", "Should have faucet service") - }) - } -} - -// createTestServiceMap creates a service map based on the provided scenario output -func createTestServiceMap() inspect.ServiceMap { - services := inspect.ServiceMap{ - // L1 Services - must match pattern expected by triageNode function - "cl-1-teku-geth": &inspect.Service{ - Ports: inspect.PortMap{ - "http": &descriptors.PortInfo{Port: 32777}, - "metrics": &descriptors.PortInfo{Port: 32778}, - "tcp-discovery": &descriptors.PortInfo{Port: 32779}, - "udp-discovery": &descriptors.PortInfo{Port: 32769}, - }, - }, - "el-1-geth-teku": &inspect.Service{ - Ports: inspect.PortMap{ - "engine-rpc": &descriptors.PortInfo{Port: 32774}, - "metrics": &descriptors.PortInfo{Port: 32775}, - "rpc": &descriptors.PortInfo{Port: 32772}, - "tcp-discovery": &descriptors.PortInfo{Port: 32776}, - "udp-discovery": &descriptors.PortInfo{Port: 32768}, - "ws": &descriptors.PortInfo{Port: 32773}, - }, - }, - "fileserver": &inspect.Service{ - Ports: inspect.PortMap{ - "http": &descriptors.PortInfo{Port: 32771}, - }, - }, - "grafana": &inspect.Service{ - Ports: inspect.PortMap{ - "http": &descriptors.PortInfo{Port: 32815}, - }, - }, - "prometheus": &inspect.Service{ - Ports: inspect.PortMap{ - "http": &descriptors.PortInfo{Port: 32814}, - }, - }, - - // L2 Chain1 Services - "op-batcher-op-kurtosis-1": &inspect.Service{ - Ports: inspect.PortMap{ - "http": &descriptors.PortInfo{Port: 32791}, - "metrics": &descriptors.PortInfo{Port: 32792}, - }, - Labels: map[string]string{ - kindLabel: "batcher", - networkIDLabel: "2151908", - }, - }, - "op-proposer-op-kurtosis-1": &inspect.Service{ - Ports: inspect.PortMap{ - "http": &descriptors.PortInfo{Port: 32793}, - "metrics": &descriptors.PortInfo{Port: 32794}, - }, - Labels: map[string]string{ - kindLabel: "proposer", - networkIDLabel: "2151908", - }, - }, - "op-cl-2151908-1": &inspect.Service{ - Ports: inspect.PortMap{ - "http": &descriptors.PortInfo{Port: 32785}, - "metrics": &descriptors.PortInfo{Port: 32786}, - "rpc-interop": &descriptors.PortInfo{Port: 32788}, - "tcp-discovery": &descriptors.PortInfo{Port: 32787}, - "udp-discovery": &descriptors.PortInfo{Port: 32771}, - }, - Labels: map[string]string{ - kindLabel: "cl", - networkIDLabel: "2151908", - nodeIndexLabel: "0", - }, - }, - "op-el-2151908-1": &inspect.Service{ - Ports: inspect.PortMap{ - "engine-rpc": &descriptors.PortInfo{Port: 32782}, - "metrics": &descriptors.PortInfo{Port: 32783}, - "rpc": &descriptors.PortInfo{Port: 32780}, - "tcp-discovery": &descriptors.PortInfo{Port: 32784}, - "udp-discovery": &descriptors.PortInfo{Port: 32770}, - "ws": &descriptors.PortInfo{Port: 32781}, - }, - Labels: map[string]string{ - kindLabel: "el", - networkIDLabel: "2151908", - nodeIndexLabel: "0", - }, - }, - "proxyd-2151908": &inspect.Service{ - Ports: inspect.PortMap{ - "http": &descriptors.PortInfo{Port: 32790}, - "metrics": &descriptors.PortInfo{Port: 32789}, - }, - Labels: map[string]string{ - kindLabel: "proxyd", - networkIDLabel: "2151908", - }, - }, - - // L2 Chain2 Services - "op-batcher-op-kurtosis-2": &inspect.Service{ - Ports: inspect.PortMap{ - "http": &descriptors.PortInfo{Port: 32806}, - "metrics": &descriptors.PortInfo{Port: 32807}, - }, - Labels: map[string]string{ - kindLabel: "batcher", - networkIDLabel: "2151909", - }, - }, - "op-proposer-op-kurtosis-2": &inspect.Service{ - Ports: inspect.PortMap{ - "http": &descriptors.PortInfo{Port: 32808}, - "metrics": &descriptors.PortInfo{Port: 32809}, - }, - Labels: map[string]string{ - kindLabel: "proposer", - networkIDLabel: "2151909", - }, - }, - "op-cl-2151909-1": &inspect.Service{ - Ports: inspect.PortMap{ - "http": &descriptors.PortInfo{Port: 32800}, - "metrics": &descriptors.PortInfo{Port: 32801}, - "rpc-interop": &descriptors.PortInfo{Port: 32803}, - "tcp-discovery": &descriptors.PortInfo{Port: 32802}, - "udp-discovery": &descriptors.PortInfo{Port: 32773}, - }, - Labels: map[string]string{ - kindLabel: "cl", - networkIDLabel: "2151909", - nodeIndexLabel: "0", - }, - }, - "op-el-2151909-1": &inspect.Service{ - Ports: inspect.PortMap{ - "engine-rpc": &descriptors.PortInfo{Port: 32797}, - "metrics": &descriptors.PortInfo{Port: 32798}, - "rpc": &descriptors.PortInfo{Port: 32795}, - "tcp-discovery": &descriptors.PortInfo{Port: 32799}, - "udp-discovery": &descriptors.PortInfo{Port: 32772}, - "ws": &descriptors.PortInfo{Port: 32796}, - }, - Labels: map[string]string{ - kindLabel: "el", - networkIDLabel: "2151909", - nodeIndexLabel: "0", - }, - }, - "proxyd-2151909": &inspect.Service{ - Ports: inspect.PortMap{ - "http": &descriptors.PortInfo{Port: 32805}, - "metrics": &descriptors.PortInfo{Port: 32804}, - }, - Labels: map[string]string{ - kindLabel: "proxyd", - networkIDLabel: "2151909", - }, - }, - - // Shared L2 Services - "op-faucet": &inspect.Service{ - Ports: inspect.PortMap{ - "rpc": &descriptors.PortInfo{Port: 32813}, - }, - Labels: map[string]string{ - kindLabel: "faucet", - }, - }, - "challenger-service": &inspect.Service{ // intentionally not following conventions, to force use of labels. - Ports: inspect.PortMap{ - "metrics": &descriptors.PortInfo{Port: 32812}, - }, - Labels: map[string]string{ - kindLabel: "challenger", - networkIDLabel: "2151908-2151909", - }, - }, - "op-supervisor-service-superchain": &inspect.Service{ - Ports: inspect.PortMap{ - "metrics": &descriptors.PortInfo{Port: 32811}, - "rpc": &descriptors.PortInfo{Port: 32810}, - }, - Labels: map[string]string{ - kindLabel: "supervisor", - supervisorSuperchainLabel: "superchain", - }, - }, - "validator-key-generation-cl-validator-keystore": {}, - } - - return services -} - -// createTestDepSets creates test dependency sets for the test -func createTestDepSets(t *testing.T) map[string]descriptors.DepSet { - // Create the dependency set for the superchain - depSetData := map[eth.ChainID]*depset.StaticConfigDependency{ - eth.ChainIDFromUInt64(2151908): {}, - eth.ChainIDFromUInt64(2151909): {}, - } - - depSet, err := depset.NewStaticConfigDependencySet(depSetData) - require.NoError(t, err) - - jsonData, err := json.Marshal(depSet) - require.NoError(t, err) - - return map[string]descriptors.DepSet{ - "superchain": descriptors.DepSet(jsonData), - } -} - -// TestTriageFunctions tests the actual implementation of triage functions -func TestTriageFunctions(t *testing.T) { - l1Spec := &spec.ChainSpec{NetworkID: "123456"} - // Create a minimal finder with default values - finder := &ServiceFinder{ - services: make(inspect.ServiceMap), - l1Chain: l1Spec, - } - - // Test the triageNode function for recognizing services - t.Run("triageNode", func(t *testing.T) { - // Test CL node parser - parser := finder.triageNode("cl-") - - // Test L1 node format - idx, accept, ok := parser("cl-1-teku-geth") - assert.True(t, ok, "Should recognize L1 CL node") - assert.Equal(t, 0, idx, "Should extract index 0 from L1 CL node") - assert.True(t, accept(l1Spec), "Should accept L1") - - // Test with various suffixes to see what is recognized - _, _, ok = parser("cl-1-teku-geth-with-extra-parts") - assert.True(t, ok, "Should recognize L1 CL node regardless of suffix") - - // This is considered invalid - _, _, ok = parser("cl") - assert.False(t, ok, "Should not recognize simple 'cl'") - }) -} - -// TestReorderNodes tests the reorderNodes function with various scenarios -func TestReorderNodes(t *testing.T) { - // Define common nodes to reduce repetition - regularNode1 := descriptors.Node{Name: "node1", Services: descriptors.ServiceMap{}} - regularNode2 := descriptors.Node{Name: "node2", Services: descriptors.ServiceMap{}} - regularNode3 := descriptors.Node{Name: "node3", Services: descriptors.ServiceMap{}} - - sequencerNode := descriptors.Node{ - Name: "sequencer", - Labels: map[string]string{"sequencer": "true"}, - } - sequencerNode1 := descriptors.Node{ - Name: "sequencer1", - Labels: map[string]string{"sequencer": "true"}, - } - sequencerNode2 := descriptors.Node{ - Name: "sequencer2", - Labels: map[string]string{"sequencer": "true"}, - } - - opGethOpNode := descriptors.Node{ - Name: "op-node", - Services: descriptors.ServiceMap{ - "el": &descriptors.Service{Name: "op-geth-1"}, - "cl": &descriptors.Service{Name: "op-node-1"}, - }, - Labels: map[string]string{ - "elType": "op-geth", - "clType": "op-node", - }, - } - - elOnlyNode := descriptors.Node{ - Name: "el-only", - Services: descriptors.ServiceMap{ - "el": &descriptors.Service{Name: "op-geth"}, - }, - } - - clOnlyNode := descriptors.Node{ - Name: "cl-only", - Services: descriptors.ServiceMap{ - "cl": &descriptors.Service{Name: "op-node"}, - }, - } - - t.Run("empty slice", func(t *testing.T) { - nodes := []descriptors.Node{} - result := reorderNodes(nodes) - assert.Equal(t, nodes, result, "Empty slice should be returned unchanged") - }) - - t.Run("single node", func(t *testing.T) { - nodes := []descriptors.Node{regularNode1} - result := reorderNodes(nodes) - assert.Equal(t, nodes, result, "Single node should be returned unchanged") - }) - - t.Run("sequencer node first", func(t *testing.T) { - nodes := []descriptors.Node{sequencerNode, regularNode2, regularNode3} - result := reorderNodes(nodes) - assert.Equal(t, nodes, result, "Sequencer already first should remain unchanged") - }) - - t.Run("sequencer node moved to front", func(t *testing.T) { - nodes := []descriptors.Node{regularNode1, sequencerNode, regularNode3} - result := reorderNodes(nodes) - - expected := []descriptors.Node{sequencerNode, regularNode1, regularNode3} - assert.Equal(t, expected, result, "Sequencer should be moved to front") - }) - - t.Run("sequencer node at end", func(t *testing.T) { - nodes := []descriptors.Node{regularNode1, regularNode2, sequencerNode} - result := reorderNodes(nodes) - - expected := []descriptors.Node{sequencerNode, regularNode1, regularNode2} - assert.Equal(t, expected, result, "Sequencer at end should be moved to front") - }) - - t.Run("multiple sequencer nodes - first one moved", func(t *testing.T) { - nodes := []descriptors.Node{regularNode1, sequencerNode1, regularNode3, sequencerNode2} - result := reorderNodes(nodes) - - expected := []descriptors.Node{sequencerNode1, regularNode1, regularNode3, sequencerNode2} - assert.Equal(t, expected, result, "First sequencer should be moved to front") - }) - - t.Run("op-geth and op-node services moved to front", func(t *testing.T) { - nodes := []descriptors.Node{regularNode1, opGethOpNode, regularNode3} - result := reorderNodes(nodes) - - expected := []descriptors.Node{opGethOpNode, regularNode1, regularNode3} - assert.Equal(t, expected, result, "Node with op-geth and op-node should be moved to front") - }) - - t.Run("op-geth and op-node services already first", func(t *testing.T) { - nodes := []descriptors.Node{opGethOpNode, regularNode2, regularNode3} - result := reorderNodes(nodes) - assert.Equal(t, nodes, result, "Node with op-geth and op-node already first should remain unchanged") - }) - - t.Run("only el service - no reordering", func(t *testing.T) { - nodes := []descriptors.Node{regularNode1, elOnlyNode, regularNode3} - result := reorderNodes(nodes) - assert.Equal(t, nodes, result, "Node with only el service should not be reordered") - }) - - t.Run("only cl service - no reordering", func(t *testing.T) { - nodes := []descriptors.Node{regularNode1, clOnlyNode, regularNode3} - result := reorderNodes(nodes) - assert.Equal(t, nodes, result, "Node with only cl service should not be reordered") - }) - - t.Run("no special nodes - original order preserved", func(t *testing.T) { - nodes := []descriptors.Node{regularNode1, regularNode2, regularNode3} - result := reorderNodes(nodes) - assert.Equal(t, nodes, result, "Nodes without special properties should maintain original order") - }) - - t.Run("sequencer takes precedence over op-geth/op-node", func(t *testing.T) { - nodes := []descriptors.Node{opGethOpNode, sequencerNode, regularNode3} - result := reorderNodes(nodes) - - expected := []descriptors.Node{sequencerNode, opGethOpNode, regularNode3} - assert.Equal(t, expected, result, "Sequencer should take precedence over op-geth/op-node") - }) -} diff --git a/kurtosis-devnet/pkg/kurtosis/kurtosis.go b/kurtosis-devnet/pkg/kurtosis/kurtosis.go deleted file mode 100644 index ddf836a992078..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/kurtosis.go +++ /dev/null @@ -1,297 +0,0 @@ -package kurtosis - -import ( - "bytes" - "context" - "fmt" - "io" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - devnetTypes "github.com/ethereum-optimism/optimism/devnet-sdk/types" - apiInterfaces "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/run" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/wrappers" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/deployer" - srcInterfaces "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/interfaces" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" - autofixTypes "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/types" -) - -const ( - DefaultPackageName = "github.com/ethpandaops/optimism-package" - DefaultEnclave = "devnet" - - // static URL for kurtosis reverse proxy - defaultKurtosisReverseProxyURL = "http://127.0.0.1:9730" -) - -// KurtosisEnvironment represents the output of a Kurtosis deployment -type KurtosisEnvironment struct { - *descriptors.DevnetEnvironment -} - -// KurtosisDeployer handles deploying packages using Kurtosis -type KurtosisDeployer struct { - // Base directory where the deployment commands should be executed - baseDir string - // Package name to deploy - packageName string - // Dry run mode - dryRun bool - // Enclave name - enclave string - - // interfaces for kurtosis sources - enclaveSpec srcInterfaces.EnclaveSpecifier - enclaveInspecter srcInterfaces.EnclaveInspecter - enclaveObserver srcInterfaces.EnclaveObserver - jwtExtractor srcInterfaces.JWTExtractor - depsetExtractor srcInterfaces.DepsetExtractor - - // interface for kurtosis interactions - kurtosisCtx apiInterfaces.KurtosisContextInterface - - // autofix mode - autofixMode autofixTypes.AutofixMode -} - -type KurtosisDeployerOptions func(*KurtosisDeployer) - -func WithKurtosisBaseDir(baseDir string) KurtosisDeployerOptions { - return func(d *KurtosisDeployer) { - d.baseDir = baseDir - } -} - -func WithKurtosisPackageName(packageName string) KurtosisDeployerOptions { - return func(d *KurtosisDeployer) { - d.packageName = packageName - } -} - -func WithKurtosisDryRun(dryRun bool) KurtosisDeployerOptions { - return func(d *KurtosisDeployer) { - d.dryRun = dryRun - } -} - -func WithKurtosisEnclave(enclave string) KurtosisDeployerOptions { - return func(d *KurtosisDeployer) { - d.enclave = enclave - } -} - -func WithKurtosisEnclaveSpec(enclaveSpec srcInterfaces.EnclaveSpecifier) KurtosisDeployerOptions { - return func(d *KurtosisDeployer) { - d.enclaveSpec = enclaveSpec - } -} - -func WithKurtosisEnclaveInspecter(enclaveInspecter srcInterfaces.EnclaveInspecter) KurtosisDeployerOptions { - return func(d *KurtosisDeployer) { - d.enclaveInspecter = enclaveInspecter - } -} - -func WithKurtosisEnclaveObserver(enclaveObserver srcInterfaces.EnclaveObserver) KurtosisDeployerOptions { - return func(d *KurtosisDeployer) { - d.enclaveObserver = enclaveObserver - } -} - -func WithKurtosisJWTExtractor(extractor srcInterfaces.JWTExtractor) KurtosisDeployerOptions { - return func(d *KurtosisDeployer) { - d.jwtExtractor = extractor - } -} - -func WithKurtosisDepsetExtractor(extractor srcInterfaces.DepsetExtractor) KurtosisDeployerOptions { - return func(d *KurtosisDeployer) { - d.depsetExtractor = extractor - } -} - -func WithKurtosisKurtosisContext(kurtosisCtx apiInterfaces.KurtosisContextInterface) KurtosisDeployerOptions { - return func(d *KurtosisDeployer) { - d.kurtosisCtx = kurtosisCtx - } -} - -func WithKurtosisAutofixMode(autofixMode autofixTypes.AutofixMode) KurtosisDeployerOptions { - return func(d *KurtosisDeployer) { - d.autofixMode = autofixMode - } -} - -// NewKurtosisDeployer creates a new KurtosisDeployer instance -func NewKurtosisDeployer(opts ...KurtosisDeployerOptions) (*KurtosisDeployer, error) { - d := &KurtosisDeployer{ - baseDir: ".", - packageName: DefaultPackageName, - dryRun: false, - enclave: DefaultEnclave, - - enclaveSpec: &enclaveSpecAdapter{}, - enclaveInspecter: &enclaveInspectAdapter{}, - enclaveObserver: &enclaveDeployerAdapter{}, - jwtExtractor: &enclaveJWTAdapter{}, - depsetExtractor: &enclaveDepsetAdapter{}, - } - - for _, opt := range opts { - opt(d) - } - - if d.kurtosisCtx == nil { - var err error - d.kurtosisCtx, err = wrappers.GetDefaultKurtosisContext() - if err != nil { - return nil, fmt.Errorf("failed to create Kurtosis context: %w", err) - } - } - - return d, nil -} - -func (d *KurtosisDeployer) getWallets(wallets deployer.WalletList) descriptors.WalletMap { - walletMap := make(descriptors.WalletMap) - for _, wallet := range wallets { - walletMap[wallet.Name] = &descriptors.Wallet{ - Address: devnetTypes.Address(wallet.Address), - PrivateKey: wallet.PrivateKey, - } - } - return walletMap -} - -// GetEnvironmentInfo parses the input spec and inspect output to create KurtosisEnvironment -func (d *KurtosisDeployer) GetEnvironmentInfo(ctx context.Context, s *spec.EnclaveSpec) (*KurtosisEnvironment, error) { - inspectResult, err := d.enclaveInspecter.EnclaveInspect(ctx, d.enclave) - if err != nil { - return nil, fmt.Errorf("failed to parse inspect output: %w", err) - } - - // Get contract addresses - deployerData, err := d.enclaveObserver.EnclaveObserve(ctx, d.enclave) - if err != nil { - return nil, fmt.Errorf("failed to parse deployer state: %w", err) - } - - // Get JWT data - jwtData, err := d.jwtExtractor.ExtractData(ctx, d.enclave) - if err != nil { - return nil, fmt.Errorf("failed to extract JWT data: %w", err) - } - - // Get dependency set - var depsets map[string]descriptors.DepSet - if s.Features.Contains(spec.FeatureInterop) { - depsets, err = d.depsetExtractor.ExtractData(ctx, d.enclave) - if err != nil { - return nil, fmt.Errorf("failed to extract dependency set: %w", err) - } - } - - env := &KurtosisEnvironment{ - DevnetEnvironment: &descriptors.DevnetEnvironment{ - Name: d.enclave, - ReverseProxyURL: defaultKurtosisReverseProxyURL, - - L2: make([]*descriptors.L2Chain, 0, len(s.Chains)), - Features: s.Features, - DepSets: depsets, - }, - } - - // Find L1 endpoint - finder := NewServiceFinder( - inspectResult.UserServices, - WithL1Chain(&spec.ChainSpec{ - NetworkID: deployerData.L1ChainID, - Name: "Ethereum", - }), - WithL2Chains(s.Chains), - WithDepSets(depsets), - ) - if nodes, services := finder.FindL1Services(); len(nodes) > 0 { - chain := &descriptors.Chain{ - ID: deployerData.L1ChainID, - Name: "Ethereum", - Services: services, - Nodes: nodes, - JWT: jwtData.L1JWT, - Addresses: descriptors.AddressMap(deployerData.State.Addresses), - Wallets: d.getWallets(deployerData.L1ValidatorWallets), - Config: deployerData.L1ChainConfig, - } - if deployerData.State != nil { - chain.Addresses = descriptors.AddressMap(deployerData.State.Addresses) - chain.Wallets = d.getWallets(deployerData.L1ValidatorWallets) - } - env.L1 = chain - } - - // Find L2 endpoints - for _, chainSpec := range s.Chains { - nodes, services := finder.FindL2Services(chainSpec) - - chain := &descriptors.L2Chain{ - Chain: &descriptors.Chain{ - Name: chainSpec.Name, - ID: chainSpec.NetworkID, - Services: services, - Nodes: nodes, - JWT: jwtData.L2JWT, - }, - } - - // Add contract addresses if available - if deployerData.State != nil && deployerData.State.Deployments != nil { - if deployment, ok := deployerData.State.Deployments[chainSpec.NetworkID]; ok { - chain.Addresses = descriptors.AddressMap(deployment.L2Addresses) - chain.Config = deployment.Config - chain.RollupConfig = deployment.RollupConfig - chain.Wallets = d.getWallets(deployment.L2Wallets) - chain.L1Wallets = d.getWallets(deployment.L1Wallets) - } - } - - env.L2 = append(env.L2, chain) - } - - return env, nil -} - -// Deploy executes the Kurtosis deployment command with the provided input -func (d *KurtosisDeployer) Deploy(ctx context.Context, input io.Reader) (*spec.EnclaveSpec, error) { - // Parse the input spec first - inputCopy := new(bytes.Buffer) - tee := io.TeeReader(input, inputCopy) - - spec, err := d.enclaveSpec.EnclaveSpec(tee) - if err != nil { - return nil, fmt.Errorf("failed to parse input spec: %w", err) - } - - // Run kurtosis command - kurtosisRunner, err := run.NewKurtosisRunner( - run.WithKurtosisRunnerDryRun(d.dryRun), - run.WithKurtosisRunnerEnclave(d.enclave), - run.WithKurtosisRunnerKurtosisContext(d.kurtosisCtx), - ) - if err != nil { - return nil, fmt.Errorf("failed to create Kurtosis runner: %w", err) - } - - if err := kurtosisRunner.Run(ctx, d.packageName, inputCopy); err != nil { - return nil, err - } - - // If dry run, return empty environment - if d.dryRun { - return spec, nil - } - - // Get environment information - return spec, nil -} diff --git a/kurtosis-devnet/pkg/kurtosis/kurtosis_test.go b/kurtosis-devnet/pkg/kurtosis/kurtosis_test.go deleted file mode 100644 index 3e7d1ddafdc80..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/kurtosis_test.go +++ /dev/null @@ -1,567 +0,0 @@ -package kurtosis - -import ( - "context" - "fmt" - "io" - "strings" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/fake" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/deployer" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/inspect" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/jwt" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestKurtosisDeployer(t *testing.T) { - tests := []struct { - name string - opts []KurtosisDeployerOptions - wantBaseDir string - wantPkg string - wantDryRun bool - wantEnclave string - }{ - { - name: "default values", - opts: nil, - wantBaseDir: ".", - wantPkg: DefaultPackageName, - wantDryRun: false, - wantEnclave: DefaultEnclave, - }, - { - name: "with options", - opts: []KurtosisDeployerOptions{ - WithKurtosisBaseDir("/custom/dir"), - WithKurtosisPackageName("custom-package"), - WithKurtosisDryRun(true), - WithKurtosisEnclave("custom-enclave"), - }, - wantBaseDir: "/custom/dir", - wantPkg: "custom-package", - wantDryRun: true, - wantEnclave: "custom-enclave", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create a fake Kurtosis context - fakeCtx := &fake.KurtosisContext{ - EnclaveCtx: &fake.EnclaveContext{ - Responses: []interfaces.StarlarkResponse{ - &fake.StarlarkResponse{ - IsSuccessful: true, - }, - }, - }, - } - - // Add the fake context to the options - opts := append(tt.opts, WithKurtosisKurtosisContext(fakeCtx)) - - d, err := NewKurtosisDeployer(opts...) - require.NoError(t, err) - assert.Equal(t, tt.wantBaseDir, d.baseDir) - assert.Equal(t, tt.wantPkg, d.packageName) - assert.Equal(t, tt.wantDryRun, d.dryRun) - assert.Equal(t, tt.wantEnclave, d.enclave) - }) - } -} - -// fakeEnclaveInspecter implements EnclaveInspecter for testing -type fakeEnclaveInspecter struct { - result *inspect.InspectData - err error -} - -func (f *fakeEnclaveInspecter) EnclaveInspect(ctx context.Context, enclave string) (*inspect.InspectData, error) { - return f.result, f.err -} - -// fakeEnclaveObserver implements EnclaveObserver for testing -type fakeEnclaveObserver struct { - state *deployer.DeployerData - err error -} - -func (f *fakeEnclaveObserver) EnclaveObserve(ctx context.Context, enclave string) (*deployer.DeployerData, error) { - return f.state, f.err -} - -// fakeEnclaveSpecifier implements EnclaveSpecifier for testing -type fakeEnclaveSpecifier struct { - spec *spec.EnclaveSpec - err error -} - -func (f *fakeEnclaveSpecifier) EnclaveSpec(r io.Reader) (*spec.EnclaveSpec, error) { - return f.spec, f.err -} - -// fakeJWTExtractor implements interfaces.JWTExtractor for testing -type fakeJWTExtractor struct { - data *jwt.Data - err error -} - -func (f *fakeJWTExtractor) ExtractData(ctx context.Context, enclave string) (*jwt.Data, error) { - return f.data, f.err -} - -type fakeDepsetExtractor struct { - data map[string]descriptors.DepSet - err error -} - -func (f *fakeDepsetExtractor) ExtractData(ctx context.Context, enclave string) (map[string]descriptors.DepSet, error) { - return f.data, f.err -} - -// mockKurtosisContext implements interfaces.KurtosisContextInterface for testing -type mockKurtosisContext struct { - enclaveCtx interfaces.EnclaveContext - getErr error - createErr error - cleanErr error - destroyErr error -} - -func (m *mockKurtosisContext) GetEnclave(ctx context.Context, name string) (interfaces.EnclaveContext, error) { - if m.getErr != nil { - return nil, m.getErr - } - return m.enclaveCtx, nil -} - -func (m *mockKurtosisContext) GetEnclaveStatus(ctx context.Context, name string) (interfaces.EnclaveStatus, error) { - if m.getErr != nil { - return "", m.getErr - } - return interfaces.EnclaveStatusRunning, nil -} - -func (m *mockKurtosisContext) CreateEnclave(ctx context.Context, name string) (interfaces.EnclaveContext, error) { - if m.createErr != nil { - return nil, m.createErr - } - return m.enclaveCtx, nil -} - -func (m *mockKurtosisContext) Clean(ctx context.Context, destroyAll bool) ([]interfaces.EnclaveNameAndUuid, error) { - if m.cleanErr != nil { - return nil, m.cleanErr - } - return []interfaces.EnclaveNameAndUuid{}, nil -} - -func (m *mockKurtosisContext) DestroyEnclave(ctx context.Context, name string) error { - if m.destroyErr != nil { - return m.destroyErr - } - return nil -} - -func TestDeploy(t *testing.T) { - testSpec := &spec.EnclaveSpec{ - Chains: []*spec.ChainSpec{ - { - Name: "op-kurtosis", - NetworkID: "1234", - }, - }, - } - - testServices := make(inspect.ServiceMap) - testServices["el-1-geth-lighthouse"] = &inspect.Service{ - Ports: inspect.PortMap{ - "rpc": {Port: 52645}, - }, - } - - testWallets := deployer.WalletList{ - { - Name: "test-wallet", - Address: common.HexToAddress("0x123"), - PrivateKey: "0xabc", - }, - } - - tests := []struct { - name string - specErr error - inspectErr error - deployerErr error - kurtosisErr error - wantErr bool - }{ - { - name: "successful deployment", - }, - { - name: "spec error", - specErr: fmt.Errorf("spec failed"), - wantErr: true, - }, - { - name: "inspect error", - inspectErr: fmt.Errorf("inspect failed"), - wantErr: true, - }, - { - name: "kurtosis error", - kurtosisErr: fmt.Errorf("kurtosis failed"), - wantErr: true, - }, - { - name: "deployer error", - deployerErr: fmt.Errorf("deployer failed"), - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create a fake Kurtosis context that will return the test error - fakeCtx := &fake.KurtosisContext{ - EnclaveCtx: &fake.EnclaveContext{ - RunErr: tt.kurtosisErr, - // Send a successful run finished event for successful cases - Responses: []interfaces.StarlarkResponse{ - &fake.StarlarkResponse{ - IsSuccessful: !tt.wantErr, - }, - }, - }, - } - - d, err := NewKurtosisDeployer( - WithKurtosisEnclaveSpec(&fakeEnclaveSpecifier{ - spec: testSpec, - err: tt.specErr, - }), - WithKurtosisEnclaveInspecter(&fakeEnclaveInspecter{ - result: &inspect.InspectData{ - UserServices: testServices, - }, - err: tt.inspectErr, - }), - WithKurtosisEnclaveObserver(&fakeEnclaveObserver{ - state: &deployer.DeployerData{ - L1ValidatorWallets: testWallets, - }, - err: tt.deployerErr, - }), - WithKurtosisKurtosisContext(fakeCtx), - ) - require.NoError(t, err) - - _, err = d.Deploy(context.Background(), strings.NewReader("test input")) - if tt.wantErr { - assert.Error(t, err) - return - } - - require.NoError(t, err) - }) - } -} - -func TestGetEnvironmentInfo(t *testing.T) { - testSpec := &spec.EnclaveSpec{ - Chains: []*spec.ChainSpec{ - { - Name: "op-kurtosis", - NetworkID: "1234", - }, - }, - } - - // Create test services map with the expected structure - testServices := make(inspect.ServiceMap) - testServices["el-1-geth-lighthouse"] = &inspect.Service{ - Ports: inspect.PortMap{ - "rpc": &descriptors.PortInfo{Port: 52645}, - }, - } - - testWallet := &deployer.Wallet{ - Name: "test-wallet", - Address: common.HexToAddress("0x123"), - PrivateKey: "0xabc", - } - testWallets := deployer.WalletList{testWallet} - - testJWTs := &jwt.Data{ - L1JWT: "test-l1-jwt", - L2JWT: "test-l2-jwt", - } - - // Create expected L1 services - l1Services := make(descriptors.ServiceMap) - l1Services["el"] = &descriptors.Service{ - Name: "el-1-geth-lighthouse", - Endpoints: descriptors.EndpointMap{ - "rpc": &descriptors.PortInfo{Port: 52645}, - }, - } - - tests := []struct { - name string - spec *spec.EnclaveSpec - inspect *inspect.InspectData - deploy *deployer.DeployerData - jwt *jwt.Data - want *KurtosisEnvironment - wantErr bool - err error - }{ - { - name: "successful environment info with JWT", - spec: testSpec, - inspect: &inspect.InspectData{UserServices: testServices}, - deploy: &deployer.DeployerData{ - L1ValidatorWallets: testWallets, - State: &deployer.DeployerState{ - Addresses: deployer.DeploymentAddresses{ - "0x123": common.HexToAddress("0x123"), - }, - }, - L1ChainID: "0", - }, - jwt: testJWTs, - want: &KurtosisEnvironment{ - DevnetEnvironment: &descriptors.DevnetEnvironment{ - Name: DefaultEnclave, - ReverseProxyURL: defaultKurtosisReverseProxyURL, - L1: &descriptors.Chain{ - ID: "0", - Name: "Ethereum", - Services: make(descriptors.RedundantServiceMap), - Nodes: []descriptors.Node{ - { - Services: l1Services, - }, - }, - JWT: testJWTs.L1JWT, - Addresses: descriptors.AddressMap{ - "0x123": common.HexToAddress("0x123"), - }, - Wallets: descriptors.WalletMap{ - testWallet.Name: { - Address: testWallet.Address, - PrivateKey: testWallet.PrivateKey, - }, - }, - }, - L2: []*descriptors.L2Chain{ - { - Chain: &descriptors.Chain{ - Name: "op-kurtosis", - ID: "1234", - Services: make(descriptors.RedundantServiceMap), - JWT: testJWTs.L2JWT, - }, - }, - }, - DepSets: nil, - }, - }, - }, - { - name: "inspect error", - spec: testSpec, - err: fmt.Errorf("inspect failed"), - wantErr: true, - }, - { - name: "deploy error", - spec: testSpec, - inspect: &inspect.InspectData{UserServices: testServices}, - err: fmt.Errorf("deploy failed"), - wantErr: true, - }, - { - name: "jwt error", - spec: testSpec, - inspect: &inspect.InspectData{UserServices: testServices}, - deploy: &deployer.DeployerData{}, - err: fmt.Errorf("jwt failed"), - wantErr: true, - }, - { - name: "with interop feature - depset fetched", - spec: &spec.EnclaveSpec{ - Chains: []*spec.ChainSpec{ - { - Name: "op-kurtosis", - NetworkID: "1234", - }, - }, - Features: spec.FeatureList{spec.FeatureInterop}, - }, - inspect: &inspect.InspectData{UserServices: testServices}, - deploy: &deployer.DeployerData{ - L1ValidatorWallets: testWallets, - State: &deployer.DeployerState{ - Addresses: deployer.DeploymentAddresses{ - "0x123": common.HexToAddress("0x123"), - }, - }, - L1ChainID: "0", - }, - jwt: testJWTs, - want: &KurtosisEnvironment{ - DevnetEnvironment: &descriptors.DevnetEnvironment{ - Name: DefaultEnclave, - ReverseProxyURL: defaultKurtosisReverseProxyURL, - L1: &descriptors.Chain{ - ID: "0", - Name: "Ethereum", - Services: make(descriptors.RedundantServiceMap), - Nodes: []descriptors.Node{ - { - Services: l1Services, - }, - }, - JWT: testJWTs.L1JWT, - Addresses: descriptors.AddressMap{ - "0x123": common.HexToAddress("0x123"), - }, - Wallets: descriptors.WalletMap{ - testWallet.Name: { - Address: testWallet.Address, - PrivateKey: testWallet.PrivateKey, - }, - }, - }, - L2: []*descriptors.L2Chain{ - { - Chain: &descriptors.Chain{ - Name: "op-kurtosis", - ID: "1234", - Services: make(descriptors.RedundantServiceMap), - JWT: testJWTs.L2JWT, - }, - }, - }, - Features: spec.FeatureList{spec.FeatureInterop}, - DepSets: map[string]descriptors.DepSet{"test-dep-set": descriptors.DepSet(`{}`)}, - }, - }, - }, - { - name: "without interop feature - depset not fetched", - spec: &spec.EnclaveSpec{ - Chains: []*spec.ChainSpec{ - { - Name: "op-kurtosis", - NetworkID: "1234", - }, - }, - Features: spec.FeatureList{}, - }, - inspect: &inspect.InspectData{UserServices: testServices}, - deploy: &deployer.DeployerData{ - L1ValidatorWallets: testWallets, - State: &deployer.DeployerState{ - Addresses: deployer.DeploymentAddresses{ - "0x123": common.HexToAddress("0x123"), - }, - }, - L1ChainID: "0", - }, - jwt: testJWTs, - want: &KurtosisEnvironment{ - DevnetEnvironment: &descriptors.DevnetEnvironment{ - Name: DefaultEnclave, - ReverseProxyURL: defaultKurtosisReverseProxyURL, - L1: &descriptors.Chain{ - ID: "0", - Name: "Ethereum", - Services: make(descriptors.RedundantServiceMap), - Nodes: []descriptors.Node{ - { - Services: l1Services, - }, - }, - JWT: testJWTs.L1JWT, - Addresses: descriptors.AddressMap{ - "0x123": common.HexToAddress("0x123"), - }, - Wallets: descriptors.WalletMap{ - testWallet.Name: { - Address: testWallet.Address, - PrivateKey: testWallet.PrivateKey, - }, - }, - }, - L2: []*descriptors.L2Chain{ - { - Chain: &descriptors.Chain{ - Name: "op-kurtosis", - ID: "1234", - Services: make(descriptors.RedundantServiceMap), - JWT: testJWTs.L2JWT, - }, - }, - }, - Features: spec.FeatureList{}, - DepSets: nil, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create a mock Kurtosis context that won't try to connect to a real engine - mockCtx := &mockKurtosisContext{ - enclaveCtx: &fake.EnclaveContext{}, - } - - // Create depset data based on whether interop is enabled - var depsets map[string]descriptors.DepSet - if tt.spec != nil && tt.spec.Features.Contains(spec.FeatureInterop) { - depsets = map[string]descriptors.DepSet{"test-dep-set": descriptors.DepSet(`{}`)} - } - - deployer, err := NewKurtosisDeployer( - WithKurtosisKurtosisContext(mockCtx), - WithKurtosisEnclaveInspecter(&fakeEnclaveInspecter{ - result: tt.inspect, - err: tt.err, - }), - WithKurtosisEnclaveObserver(&fakeEnclaveObserver{ - state: tt.deploy, - err: tt.err, - }), - WithKurtosisJWTExtractor(&fakeJWTExtractor{ - data: tt.jwt, - err: tt.err, - }), - WithKurtosisDepsetExtractor(&fakeDepsetExtractor{ - data: depsets, - err: tt.err, - }), - ) - require.NoError(t, err) - - got, err := deployer.GetEnvironmentInfo(context.Background(), tt.spec) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - require.Equal(t, tt.want, got) - }) - } -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/deployer/cmd/main.go b/kurtosis-devnet/pkg/kurtosis/sources/deployer/cmd/main.go deleted file mode 100644 index 3d721a8795aaa..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/deployer/cmd/main.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "flag" - "fmt" - "os" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/deployer" -) - -func main() { - // Parse command line flags - enclave := flag.String("enclave", "", "Name of the Kurtosis enclave") - flag.Parse() - - if *enclave == "" { - fmt.Fprintln(os.Stderr, "Error: --enclave flag is required") - flag.Usage() - os.Exit(1) - } - - // Get deployer data - d := deployer.NewDeployer(*enclave) - ctx := context.Background() - data, err := d.ExtractData(ctx) - if err != nil { - fmt.Fprintf(os.Stderr, "Error parsing deployer data: %v\n", err) - os.Exit(1) - } - - // Encode as JSON and write to stdout - encoder := json.NewEncoder(os.Stdout) - encoder.SetIndent("", " ") - if err := encoder.Encode(data); err != nil { - fmt.Fprintf(os.Stderr, "Error encoding JSON: %v\n", err) - os.Exit(1) - } -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go b/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go deleted file mode 100644 index 429e5af31082c..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go +++ /dev/null @@ -1,470 +0,0 @@ -package deployer - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "math/big" - "strings" - "text/template" - - ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" - "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" -) - -const ( - defaultDeployerArtifactName = "op-deployer-configs" - defaultWalletsName = "wallets.json" - defaultStateName = "state.json" - defaultGenesisArtifactName = "el_cl_genesis_data" - defaultMnemonicName = "mnemonics.yaml" - defaultGenesisNameTemplate = "genesis-{{.ChainID}}.json" - defaultRollupNameTemplate = "rollup-{{.ChainID}}.json" - defaultL1GenesisName = "genesis.json" -) - -// DeploymentAddresses maps contract names to their addresses -type DeploymentAddresses map[string]types.Address - -// DeploymentStateAddresses maps chain IDs to their contract addresses -type DeploymentStateAddresses map[string]DeploymentAddresses - -type DeploymentState struct { - L1Addresses DeploymentAddresses `json:"l1_addresses"` - L2Addresses DeploymentAddresses `json:"l2_addresses"` - L1Wallets WalletList `json:"l1_wallets"` - L2Wallets WalletList `json:"l2_wallets"` - Config *params.ChainConfig `json:"chain_config"` - RollupConfig *rollup.Config `json:"rollup_config"` -} - -type DeployerState struct { - Deployments map[string]DeploymentState `json:"l2s"` - Addresses DeploymentAddresses `json:"superchain"` -} - -// StateFile represents the structure of the state.json file -type StateFile struct { - OpChainDeployments []map[string]interface{} `json:"opChainDeployments"` - SuperChainContracts map[string]interface{} `json:"superchainContracts"` - ImplementationsDeployment map[string]interface{} `json:"implementationsDeployment"` -} - -// Wallet represents a wallet with optional private key and name -type Wallet struct { - Address types.Address `json:"address"` - PrivateKey string `json:"private_key"` - Name string `json:"name"` -} - -// WalletList holds a list of wallets -type WalletList []*Wallet -type WalletMap map[string]*Wallet - -type DeployerData struct { - L1ValidatorWallets WalletList `json:"wallets"` - State *DeployerState `json:"state"` - L1ChainID string `json:"l1_chain_id"` - L1ChainConfig *params.ChainConfig `json:"l1_chain_config"` -} - -type Deployer struct { - enclave string - deployerArtifactName string - walletsName string - stateName string - genesisArtifactName string - l1ValidatorMnemonicName string - l2GenesisNameTemplate string - l2RollupNameTemplate string - l1GenesisName string -} - -type DeployerOption func(*Deployer) - -func WithArtifactName(name string) DeployerOption { - return func(d *Deployer) { - d.deployerArtifactName = name - } -} - -func WithWalletsName(name string) DeployerOption { - return func(d *Deployer) { - d.walletsName = name - } -} - -func WithStateName(name string) DeployerOption { - return func(d *Deployer) { - d.stateName = name - } -} - -func WithGenesisArtifactName(name string) DeployerOption { - return func(d *Deployer) { - d.genesisArtifactName = name - } -} - -func WithMnemonicsName(name string) DeployerOption { - return func(d *Deployer) { - d.l1ValidatorMnemonicName = name - } -} - -func WithGenesisNameTemplate(name string) DeployerOption { - return func(d *Deployer) { - d.l2GenesisNameTemplate = name - } -} - -func WithRollupNameTemplate(name string) DeployerOption { - return func(d *Deployer) { - d.l2RollupNameTemplate = name - } -} - -func NewDeployer(enclave string, opts ...DeployerOption) *Deployer { - d := &Deployer{ - enclave: enclave, - deployerArtifactName: defaultDeployerArtifactName, - walletsName: defaultWalletsName, - stateName: defaultStateName, - genesisArtifactName: defaultGenesisArtifactName, - l1ValidatorMnemonicName: defaultMnemonicName, - l2GenesisNameTemplate: defaultGenesisNameTemplate, - l2RollupNameTemplate: defaultRollupNameTemplate, - l1GenesisName: defaultL1GenesisName, - } - - for _, opt := range opts { - opt(d) - } - - return d -} - -// parseWalletsFile parses a JSON file containing wallet information -func parseWalletsFile(r io.Reader) (map[string]WalletList, error) { - result := make(map[string]WalletList) - - // Read all data from reader - data, err := io.ReadAll(r) - if err != nil { - return nil, fmt.Errorf("failed to read wallet file: %w", err) - } - - // Unmarshal into a map first - var rawData map[string]map[string]string - if err := json.Unmarshal(data, &rawData); err != nil { - return nil, fmt.Errorf("failed to decode wallet file: %w", err) - } - - for id, chain := range rawData { - // Create a map to store wallets by name - walletMap := make(WalletMap) - hasAddress := make(map[string]bool) - - // First pass: collect addresses - for key, value := range chain { - if strings.HasSuffix(key, "Address") { - name := strings.TrimSuffix(key, "Address") - wallet, ok := walletMap[name] - if !ok || wallet == nil { - wallet = &Wallet{ - Name: name, - Address: common.HexToAddress(value), - } - } else { - log.Warn("duplicate wallet name key in wallets file", "name", name) - } - walletMap[name] = wallet - hasAddress[name] = true - } - } - - // Second pass: collect private keys only for wallets with addresses - for key, value := range chain { - if strings.HasSuffix(key, "PrivateKey") { - name := strings.TrimSuffix(key, "PrivateKey") - if hasAddress[name] { - wallet := walletMap[name] - wallet.PrivateKey = value - walletMap[name] = wallet - } - } - } - - // Convert map to list, only including wallets with addresses - wl := make(WalletList, 0, len(walletMap)) - for name, wallet := range walletMap { - if hasAddress[name] { - wl = append(wl, wallet) - } - } - - result[id] = wl - } - - return result, nil -} - -// hexToDecimal converts a hex string (with or without 0x prefix) to a decimal string -func hexToDecimal(hex string) (string, error) { - // Remove 0x prefix if present - hex = strings.TrimPrefix(hex, "0x") - - // Parse hex string to big.Int - n := new(big.Int) - if _, ok := n.SetString(hex, 16); !ok { - return "", fmt.Errorf("invalid hex string: %s", hex) - } - - // Convert to decimal string - return n.String(), nil -} - -// parseStateFile parses the state.json file and extracts addresses -func parseStateFile(r io.Reader) (*DeployerState, error) { - var state StateFile - if err := json.NewDecoder(r).Decode(&state); err != nil { - return nil, fmt.Errorf("failed to decode state file: %w", err) - } - - result := &DeployerState{ - Deployments: make(map[string]DeploymentState), - Addresses: make(DeploymentAddresses), - } - - mapDeployment := func(deployment map[string]interface{}) DeploymentAddresses { - addresses := make(DeploymentAddresses) - for key, value := range deployment { - if strings.HasSuffix(key, "Proxy") || strings.HasSuffix(key, "Impl") { - addresses[key] = common.HexToAddress(value.(string)) - } - } - return addresses - } - - for _, deployment := range state.OpChainDeployments { - // Get the chain ID - idValue, ok := deployment["id"] - if !ok { - continue - } - hexID, ok := idValue.(string) - if !ok { - continue - } - - // Convert hex ID to decimal - id, err := hexToDecimal(hexID) - if err != nil { - continue - } - - l1Addresses := mapDeployment(deployment) - - // op-deployer currently does not categorize L2 addresses - // so we need to map them manually. - // TODO: Update op-deployer to sort rollup contracts by category - l2Addresses := make(DeploymentAddresses) - for _, addressName := range []string{"OptimismMintableErc20FactoryProxy"} { - if addr, ok := l1Addresses[addressName]; ok { - l2Addresses[addressName] = addr - delete(l1Addresses, addressName) - } - } - - result.Deployments[id] = DeploymentState{ - L1Addresses: l1Addresses, - L2Addresses: l2Addresses, - } - } - - result.Addresses = mapDeployment(state.ImplementationsDeployment) - // merge the superchain and implementations addresses - for key, value := range mapDeployment(state.SuperChainContracts) { - result.Addresses[key] = value - } - - return result, nil -} - -// ExtractData downloads and parses the op-deployer state -func (d *Deployer) ExtractData(ctx context.Context) (*DeployerData, error) { - fs, err := ktfs.NewEnclaveFS(ctx, d.enclave) - if err != nil { - return nil, err - } - - deployerArtifact, err := fs.GetArtifact(ctx, d.deployerArtifactName) - if err != nil { - return nil, err - } - - stateBuffer := bytes.NewBuffer(nil) - walletsBuffer := bytes.NewBuffer(nil) - if err := deployerArtifact.ExtractFiles( - ktfs.NewArtifactFileWriter(d.stateName, stateBuffer), - ktfs.NewArtifactFileWriter(d.walletsName, walletsBuffer), - ); err != nil { - return nil, err - } - - state, err := parseStateFile(stateBuffer) - if err != nil { - return nil, err - } - - l1WalletsForL2Admin, err := parseWalletsFile(walletsBuffer) - if err != nil { - return nil, err - } - - // Generate test wallets from the standard "test test test..." mnemonic - // These are the same wallets funded in L2Genesis.s.sol's devAccounts array - devWallets, err := d.getDevWallets() - if err != nil { - return nil, err - } - - for id, deployment := range state.Deployments { - if l1Wallets, exists := l1WalletsForL2Admin[id]; exists { - deployment.L1Wallets = l1Wallets - } - deployment.L2Wallets = devWallets - - genesisBuffer := bytes.NewBuffer(nil) - genesisName, err := d.renderGenesisNameTemplate(id) - if err != nil { - return nil, err - } - - if err := deployerArtifact.ExtractFiles( - ktfs.NewArtifactFileWriter(genesisName, genesisBuffer), - ); err != nil { - return nil, err - } - - // Parse the genesis file JSON into a core.Genesis struct - var genesis core.Genesis - if err := json.NewDecoder(genesisBuffer).Decode(&genesis); err != nil { - return nil, fmt.Errorf("failed to parse genesis file %s in artifact %s for chain ID %s: %w", genesisName, d.deployerArtifactName, id, err) - } - - // Store the genesis data in the deployment state - deployment.Config = genesis.Config - - rollupBuffer := bytes.NewBuffer(nil) - rollupName, err := d.renderRollupNameTemplate(id) - if err != nil { - return nil, err - } - - if err := deployerArtifact.ExtractFiles( - ktfs.NewArtifactFileWriter(rollupName, rollupBuffer), - ); err != nil { - return nil, err - } - - // Parse the genesis file JSON into a core.Genesis struct - var rollupCfg rollup.Config - if err := json.NewDecoder(rollupBuffer).Decode(&rollupCfg); err != nil { - return nil, fmt.Errorf("failed to parse rollup file %s in artifact %s for chain ID %s: %w", rollupName, d.deployerArtifactName, id, err) - } - - // Store the data in the deployment state - deployment.Config = genesis.Config - deployment.RollupConfig = &rollupCfg - - state.Deployments[id] = deployment - } - - l1GenesisArtifact, err := fs.GetArtifact(ctx, d.genesisArtifactName) - if err != nil { - return nil, err - } - - l1ValidatorWallets, err := d.getL1ValidatorWallets(l1GenesisArtifact) - if err != nil { - return nil, err - } - - l1ChainConfig, err := d.getConfig(l1GenesisArtifact) - if err != nil { - return nil, err - } - - return &DeployerData{ - L1ChainID: l1ChainConfig.ChainID.String(), - State: state, - L1ValidatorWallets: l1ValidatorWallets, - L1ChainConfig: l1ChainConfig, - }, nil -} - -func (d *Deployer) renderGenesisNameTemplate(chainID string) (string, error) { - return d.renderNameTemplate(d.l2GenesisNameTemplate, chainID) -} - -func (d *Deployer) renderRollupNameTemplate(chainID string) (string, error) { - return d.renderNameTemplate(d.l2RollupNameTemplate, chainID) -} - -func (d *Deployer) renderNameTemplate(t, chainID string) (string, error) { - tmpl, err := template.New("").Parse(t) - if err != nil { - return "", fmt.Errorf("failed to compile name template %s: %w", t, err) - } - - var buf bytes.Buffer - err = tmpl.Execute(&buf, map[string]string{"ChainID": chainID}) - if err != nil { - return "", fmt.Errorf("failed to execute name template %s: %w", t, err) - } - - return buf.String(), nil -} - -// getDevWallets generates the set of test wallets used in L2Genesis.s.sol -// These wallets are derived from the standard test mnemonic -func (d *Deployer) getDevWallets() ([]*Wallet, error) { - m, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) - if err != nil { - return nil, fmt.Errorf("failed to create mnemonic dev keys: %w", err) - } - - // Generate 30 wallets to match L2Genesis.s.sol's devAccounts array - testWallets := make([]*Wallet, 0, 30) - for i := 0; i < 30; i++ { - key := devkeys.UserKey(uint64(i)) - addr, err := m.Address(key) - if err != nil { - return nil, fmt.Errorf("failed to get address for test wallet %d: %w", i, err) - } - - sec, err := m.Secret(key) - if err != nil { - return nil, fmt.Errorf("failed to get secret key for test wallet %d: %w", i, err) - } - - testWallets = append(testWallets, &Wallet{ - Name: fmt.Sprintf("dev-account-%d", i), - Address: addr, - PrivateKey: hexutil.Bytes(crypto.FromECDSA(sec)).String(), - }) - } - - return testWallets, nil -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer_test.go b/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer_test.go deleted file mode 100644 index bb5aeb15a8466..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer_test.go +++ /dev/null @@ -1,220 +0,0 @@ -package deployer - -import ( - "sort" - "strings" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" -) - -func TestParseStateFile(t *testing.T) { - stateJSON := `{ - "opChainDeployments": [ - { - "id": "0x000000000000000000000000000000000000000000000000000000000020d5e4", - "FooProxy": "0x123", - "FooImpl": "0x456", - "FooBar": "0x789" - }, - { - "id": "0x000000000000000000000000000000000000000000000000000000000020d5e5", - "FooProxy": "0xabc", - "FooImpl": "0xdef" - } - ] - }` - - result, err := parseStateFile(strings.NewReader(stateJSON)) - require.NoError(t, err, "Failed to parse state file") - - // Test chain deployments - tests := []struct { - chainID string - expected DeploymentAddresses - }{ - { - chainID: "2151908", - expected: DeploymentAddresses{ - "FooProxy": common.HexToAddress("0x123"), - "FooImpl": common.HexToAddress("0x456"), - }, - }, - { - chainID: "2151909", - expected: DeploymentAddresses{ - "FooProxy": common.HexToAddress("0xabc"), - "FooImpl": common.HexToAddress("0xdef"), - }, - }, - } - - for _, tt := range tests { - chain, ok := result.Deployments[tt.chainID] - require.True(t, ok, "Chain %s not found in result", tt.chainID) - - for key, expected := range tt.expected { - actual := chain.L1Addresses[key] - // TODO: add L2 addresses - require.Equal(t, expected, actual, "Chain %s, %s: expected %s, got %s", tt.chainID, key, expected, actual) - } - } -} - -func TestParseStateFileErrors(t *testing.T) { - tests := []struct { - name string - json string - wantErr bool - }{ - { - name: "empty json", - json: "", - wantErr: true, - }, - { - name: "invalid json", - json: "{invalid", - wantErr: true, - }, - { - name: "missing deployments", - json: `{ - "otherField": [] - }`, - wantErr: false, - }, - { - name: "invalid address type", - json: `{ - "opChainDeployments": [ - { - "id": "3151909", - "data": { - "L1CrossDomainMessengerAddress": 123 - } - } - ] - }`, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, err := parseStateFile(strings.NewReader(tt.json)) - if tt.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestParseWalletsFile(t *testing.T) { - tests := []struct { - name string - input string - want map[string]WalletList - wantErr bool - }{ - { - name: "successful parse", - input: `{ - "chain1": { - "proposerPrivateKey": "0xe1ec816e9ad0372e458c474a06e1e6d9e7f7985cbf642a5e5fa44be639789531", - "proposerAddress": "0xDFfA3C478Be83a91286c04721d2e5DF9A133b93F", - "batcherPrivateKey": "0x557313b816b8fb354340883edf86627b3de680a9f3e15aa1f522cbe6f9c7b967", - "batcherAddress": "0x6bd90c2a1AE00384AD9F4BcD76310F54A9CcdA11" - } - }`, - want: map[string]WalletList{ - "chain1": { - { - Name: "proposer", - Address: common.HexToAddress("0xDFfA3C478Be83a91286c04721d2e5DF9A133b93F"), - PrivateKey: "0xe1ec816e9ad0372e458c474a06e1e6d9e7f7985cbf642a5e5fa44be639789531", - }, - { - Name: "batcher", - Address: common.HexToAddress("0x6bd90c2a1AE00384AD9F4BcD76310F54A9CcdA11"), - PrivateKey: "0x557313b816b8fb354340883edf86627b3de680a9f3e15aa1f522cbe6f9c7b967", - }, - }, - }, - wantErr: false, - }, - { - name: "address only", - input: `{ - "chain1": { - "proposerAddress": "0xDFfA3C478Be83a91286c04721d2e5DF9A133b93F" - } - }`, - want: map[string]WalletList{ - "chain1": { - { - Name: "proposer", - Address: common.HexToAddress("0xDFfA3C478Be83a91286c04721d2e5DF9A133b93F"), - }, - }, - }, - wantErr: false, - }, - { - name: "private key only - should be ignored", - input: `{ - "chain1": { - "proposerPrivateKey": "0xe1ec816e9ad0372e458c474a06e1e6d9e7f7985cbf642a5e5fa44be639789531" - } - }`, - want: map[string]WalletList{ - "chain1": {}, - }, - wantErr: false, - }, - { - name: "invalid JSON", - input: `{invalid json}`, - want: nil, - wantErr: true, - }, - { - name: "empty input", - input: `{}`, - want: map[string]WalletList{}, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - reader := strings.NewReader(tt.input) - got, err := parseWalletsFile(reader) - - if tt.wantErr { - require.Error(t, err) - return - } - - require.NoError(t, err) - require.NotNil(t, got) - - // Sort wallets by name for consistent comparison - sortWallets := func(wallets WalletList) { - sort.Slice(wallets, func(i, j int) bool { - return wallets[i].Name < wallets[j].Name - }) - } - - for chainID, wallets := range got { - sortWallets(wallets) - wantWallets := tt.want[chainID] - sortWallets(wantWallets) - require.Equal(t, wantWallets, wallets) - } - }) - } -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/deployer/wallets.go b/kurtosis-devnet/pkg/kurtosis/sources/deployer/wallets.go deleted file mode 100644 index 2cc1873618387..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/deployer/wallets.go +++ /dev/null @@ -1,89 +0,0 @@ -package deployer - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - - ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" - "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/params" - "gopkg.in/yaml.v3" -) - -const ( - // TODO: can we figure out how many were actually funded? - numWallets = 21 -) - -func getMnemonics(r io.Reader) (string, error) { - type mnemonicConfig struct { - Mnemonic string `yaml:"mnemonic"` - Count int `yaml:"count"` // TODO: what does this mean? it seems much larger than the number of wallets - } - - var config []mnemonicConfig - decoder := yaml.NewDecoder(r) - if err := decoder.Decode(&config); err != nil { - return "", fmt.Errorf("failed to decode mnemonic config: %w", err) - } - - // TODO: what does this mean if there are multiple mnemonics in this file? - return config[0].Mnemonic, nil -} - -func (d *Deployer) getL1ValidatorWallets(deployerArtifact *ktfs.Artifact) ([]*Wallet, error) { - mnemonicsBuffer := bytes.NewBuffer(nil) - if err := deployerArtifact.ExtractFiles( - ktfs.NewArtifactFileWriter(d.l1ValidatorMnemonicName, mnemonicsBuffer), - ); err != nil { - return nil, err - } - - mnemonic, err := getMnemonics(mnemonicsBuffer) - if err != nil { - return nil, err - } - - m, _ := devkeys.NewMnemonicDevKeys(mnemonic) - knownWallets := make([]*Wallet, 0) - - var keys []devkeys.Key - for i := 0; i < numWallets; i++ { - keys = append(keys, devkeys.UserKey(i)) - } - - for _, key := range keys { - addr, _ := m.Address(key) - sec, _ := m.Secret(key) - - knownWallets = append(knownWallets, &Wallet{ - Name: key.String(), - Address: addr, - PrivateKey: hexutil.Bytes(crypto.FromECDSA(sec)).String(), - }) - } - - return knownWallets, nil -} - -func (d *Deployer) getConfig(genesisArtifact *ktfs.Artifact) (*params.ChainConfig, error) { - genesisBuffer := bytes.NewBuffer(nil) - if err := genesisArtifact.ExtractFiles( - ktfs.NewArtifactFileWriter(d.l1GenesisName, genesisBuffer), - ); err != nil { - return nil, err - } - - // Parse the genesis file JSON into a core.Genesis struct - var genesis core.Genesis - if err := json.NewDecoder(genesisBuffer).Decode(&genesis); err != nil { - return nil, fmt.Errorf("failed to parse genesis file %s in artifact %s: %w", d.l1GenesisName, d.genesisArtifactName, err) - } - - return genesis.Config, nil -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/depset/cmd/main.go b/kurtosis-devnet/pkg/kurtosis/sources/depset/cmd/main.go deleted file mode 100644 index d14651ed6a895..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/depset/cmd/main.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "flag" - "fmt" - "os" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/depset" -) - -func main() { - // Parse command line flags - enclave := flag.String("enclave", "", "Name of the Kurtosis enclave") - flag.Parse() - - if *enclave == "" { - fmt.Fprintln(os.Stderr, "Error: --enclave flag is required") - flag.Usage() - os.Exit(1) - } - - // Get depset data - e := depset.NewExtractor(*enclave) - ctx := context.Background() - data, err := e.ExtractData(ctx) - if err != nil { - fmt.Fprintf(os.Stderr, "Error parsing deployer data: %v\n", err) - os.Exit(1) - } - - for name, depset := range data { - fmt.Println(name) - // Encode as JSON and write to stdout - encoder := json.NewEncoder(os.Stdout) - encoder.SetIndent("", " ") - if err := encoder.Encode(depset); err != nil { - fmt.Fprintf(os.Stderr, "Error encoding JSON: %v\n", err) - os.Exit(1) - } - } -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/depset/depset.go b/kurtosis-devnet/pkg/kurtosis/sources/depset/depset.go deleted file mode 100644 index ca10e0cdc48a2..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/depset/depset.go +++ /dev/null @@ -1,79 +0,0 @@ -package depset - -import ( - "bytes" - "context" - "fmt" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/util" -) - -const ( - depsetFileNamePrefix = "superchain-depset-" -) - -// extractor implements the interfaces.DepsetExtractor interface -type extractor struct { - enclave string -} - -// NewExtractor creates a new dependency set extractor -func NewExtractor(enclave string) *extractor { - return &extractor{ - enclave: enclave, - } -} - -// ExtractData extracts dependency set from its respective artifact -func (e *extractor) ExtractData(ctx context.Context) (map[string]descriptors.DepSet, error) { - fs, err := ktfs.NewEnclaveFS(ctx, e.enclave) - if err != nil { - return nil, err - } - - return extractDepsetsFromArtifacts(ctx, fs) -} - -func extractDepsetsFromArtifacts(ctx context.Context, fs *ktfs.EnclaveFS) (map[string]descriptors.DepSet, error) { - // Get all artifact names with retry logic - allArtifacts, err := util.WithRetry(ctx, "GetAllArtifactNames", func() ([]string, error) { - return fs.GetAllArtifactNames(ctx) - }) - - if err != nil { - return nil, fmt.Errorf("failed to get all artifact names: %w", err) - } - - depsetArtifacts := make([]string, 0) - for _, artifactName := range allArtifacts { - if strings.HasPrefix(artifactName, depsetFileNamePrefix) { - depsetArtifacts = append(depsetArtifacts, artifactName) - } - } - - depsets := make(map[string]descriptors.DepSet) - for _, artifactName := range depsetArtifacts { - // Get artifact with retry logic - a, err := util.WithRetry(ctx, fmt.Sprintf("GetArtifact(%s)", artifactName), func() (*ktfs.Artifact, error) { - return fs.GetArtifact(ctx, artifactName) - }) - - if err != nil { - return nil, fmt.Errorf("failed to get artifact '%s': %w", artifactName, err) - } - - fname := artifactName + ".json" - buffer := &bytes.Buffer{} - if err := a.ExtractFiles(ktfs.NewArtifactFileWriter(fname, buffer)); err != nil { - return nil, fmt.Errorf("failed to extract dependency set: %w", err) - } - - depsetName := strings.TrimPrefix(artifactName, depsetFileNamePrefix) - depsets[depsetName] = descriptors.DepSet(buffer.Bytes()) - } - - return depsets, nil -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/inspect/README.md b/kurtosis-devnet/pkg/kurtosis/sources/inspect/README.md deleted file mode 100644 index 95ce2688d2eab..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/inspect/README.md +++ /dev/null @@ -1,351 +0,0 @@ -# Kurtosis Inspect Tool - -A command-line tool for inspecting Kurtosis enclaves and extracting conductor configurations and environment data from running Optimism devnets. - -## Overview - -The Kurtosis Inspect Tool provides a clean interface to: - -- 🔍 **Inspect running Kurtosis enclaves** - Extract service information and file artifacts -- 🎛️ **Generate conductor configurations** - Create TOML configs for `op-conductor-ops` -- 📊 **Export environment data** - Save complete devnet information as JSON -- 🔧 **Fix Traefik issues** - Repair missing network labels on containers - -## Installation - -### Build from Source - -```bash -cd optimism/kurtosis-devnet -go build -o kurtosis-inspect pkg/kurtosis/sources/inspect/cmd/main.go -``` - -### Run Directly - -```bash -go run pkg/kurtosis/sources/inspect/cmd/main.go [options] -``` - -## Usage - -### Basic Inspection - -Inspect a running enclave and display results: - -```bash -./kurtosis-inspect my-devnet-enclave -``` - -### Extract Conductor Configuration - -Generate a conductor configuration file for use with `op-conductor-ops`: - -```bash -./kurtosis-inspect --conductor-config conductor.toml my-devnet-enclave -``` - -### Export Complete Environment - -Save the complete environment data as JSON: - -```bash -./kurtosis-inspect --environment environment.json my-devnet-enclave -``` - -### Combined Export - -Extract both conductor config and environment data: - -```bash -./kurtosis-inspect \ - --conductor-config conductor.toml \ - --environment environment.json \ - my-devnet-enclave -``` - -### Fix Traefik Network Issues - -Repair missing Traefik labels on containers: - -```bash -./kurtosis-inspect --fix-traefik my-devnet-enclave -``` - -## Configuration Options - -### CLI Flags - -| Flag | Environment Variable | Description | -|------|---------------------|-------------| -| `--conductor-config` | `KURTOSIS_INSPECT_CONDUCTOR_CONFIG` | Path to write conductor configuration TOML file | -| `--environment` | `KURTOSIS_INSPECT_ENVIRONMENT` | Path to write environment JSON file | -| `--fix-traefik` | `KURTOSIS_INSPECT_FIX_TRAEFIK` | Fix missing Traefik labels on containers | -| `--log.level` | `KURTOSIS_INSPECT_LOG_LEVEL` | Logging level (DEBUG, INFO, WARN, ERROR) | -| `--log.format` | `KURTOSIS_INSPECT_LOG_FORMAT` | Log format (text, json, logfmt) | - -### Environment Variables - -All flags can be set via environment variables with the `KURTOSIS_INSPECT_` prefix: - -```bash -export KURTOSIS_INSPECT_CONDUCTOR_CONFIG="/tmp/conductor.toml" -export KURTOSIS_INSPECT_ENVIRONMENT="/tmp/environment.json" -export KURTOSIS_INSPECT_LOG_LEVEL="DEBUG" - -./kurtosis-inspect my-devnet-enclave -``` - -## Output Formats - -### Conductor Configuration (TOML) - -The conductor configuration file is compatible with `op-conductor-ops`: - -```toml -[networks] - [networks.2151908-chain0-kona] - sequencers = ["op-conductor-2151908-chain0-kona-sequencer"] - [networks.2151908-chain0-optimism] - sequencers = ["op-conductor-2151908-chain0-optimism-sequencer"] - -[sequencers] - [sequencers.op-conductor-2151908-chain0-kona-sequencer] - raft_addr = "127.0.0.1:60135" - conductor_rpc_url = "http://127.0.0.1:60134" - node_rpc_url = "http://127.0.0.1:60048" - voting = true - [sequencers.op-conductor-2151908-chain0-optimism-sequencer] - raft_addr = "127.0.0.1:60176" - conductor_rpc_url = "http://127.0.0.1:60177" - node_rpc_url = "http://127.0.0.1:60062" - voting = true -``` - -### Environment Data (JSON) - -Complete environment data including services and file artifacts: - -```json -{ - "FileArtifacts": [ - "genesis-l1.json", - "genesis-l2-chain0.json", - "jwt.txt", - "rollup-l2-chain0.json" - ], - "UserServices": { - "op-node-chain0-sequencer": { - "Labels": { - "app": "op-node", - "chain": "chain0", - "role": "sequencer" - }, - "Ports": { - "rpc": { - "Host": "127.0.0.1", - "Port": 9545 - }, - "p2p": { - "Host": "127.0.0.1", - "Port": 9222 - } - } - } - } -} -``` - -## Integration with op-conductor-ops - -### 1. Generate Conductor Configuration - -```bash -# Extract conductor config from running devnet -./kurtosis-inspect --conductor-config conductor.toml my-devnet - -# Use with op-conductor-ops -cd infra/op-conductor-ops -python op-conductor-ops.py --config ../../kurtosis-devnet/conductor.toml status -``` - -### 2. Leadership Transfer Example - -```bash -# Generate config and perform leadership transfer -./kurtosis-inspect --conductor-config conductor.toml my-devnet -cd infra/op-conductor-ops -python op-conductor-ops.py --config ../../kurtosis-devnet/conductor.toml \ - transfer-leadership \ - --target-sequencer "op-conductor-2151908-chain0-optimism-sequencer" -``` - -## Examples - -### Simple Devnet - -```bash -# Deploy simple devnet -cd kurtosis-devnet -just devnet simple - -# Inspect and extract configs -./kurtosis-inspect --conductor-config tests/simple-conductor.toml simple-devnet - -# Check conductor status -cd ../infra/op-conductor-ops -python op-conductor-ops.py --config ../../kurtosis-devnet/tests/simple-conductor.toml status -``` - -### Multi-Chain Interop - -```bash -# Deploy interop devnet -just devnet interop - -# Extract complex conductor configuration -./kurtosis-inspect \ - --conductor-config tests/interop-conductor.toml \ - --environment tests/interop-environment.json \ - interop-devnet - -# View conductor cluster status -cd ../infra/op-conductor-ops -python op-conductor-ops.py --config ../../kurtosis-devnet/tests/interop-conductor.toml status -``` - -### Debugging Network Issues - -```bash -# Fix Traefik network issues -./kurtosis-inspect --fix-traefik my-devnet - -# Inspect with debug logging -./kurtosis-inspect --log.level DEBUG --log.format json my-devnet -``` - -## Architecture - -The tool follows a clean architecture pattern with clear separation of concerns: - -``` -pkg/kurtosis/sources/inspect/ -├── cmd/main.go # CLI setup and entry point -├── config.go # Configuration parsing and validation -├── service.go # Business logic and service layer -├── conductor.go # Conductor configuration extraction -├── inspect.go # Core inspection functionality -├── flags/ -│ ├── flags.go # CLI flag definitions -│ └── flags_test.go # Flag testing -└── *_test.go # Comprehensive test suite -``` - -### Key Components - -- **Config**: Handles CLI argument parsing and validation -- **InspectService**: Main business logic for inspection operations -- **ConductorConfig**: Data structures for conductor configuration -- **Inspector**: Core enclave inspection functionality - -## Testing - -### Run All Tests - -```bash -go test ./pkg/kurtosis/sources/inspect/... -v -``` - -### Test Coverage - -```bash -go test ./pkg/kurtosis/sources/inspect/... -cover -``` - -### Test Categories - -- **Unit Tests**: Individual component functionality -- **Integration Tests**: File I/O and configuration parsing -- **Real-World Tests**: Based on actual devnet configurations -- **Error Tests**: Permission and validation error handling - -## Troubleshooting - -### Common Issues - -#### Kurtosis Engine Not Running - -``` -Error: failed to create Kurtosis context: The Kurtosis Engine Server is unavailable -``` - -**Solution:** -```bash -kurtosis engine start -``` - -#### Enclave Not Found - -``` -Error: failed to get enclave: enclave with identifier 'my-devnet' not found -``` - -**Solution:** -```bash -# List available enclaves -kurtosis enclave ls - -# Use correct enclave name -./kurtosis-inspect -``` - -#### Permission Denied - -``` -Error: error creating conductor config file: permission denied -``` - -**Solution:** -```bash -# Ensure write permissions to output directory -chmod 755 /output/directory -``` - -### Debug Mode - -Enable debug logging for detailed troubleshooting: - -```bash -./kurtosis-inspect --log.level DEBUG --log.format json my-devnet -``` - -## Contributing - -### Development Setup - -```bash -# Install dependencies -go mod download - -# Run tests -go test ./pkg/kurtosis/sources/inspect/... -v - -# Build -go build -o kurtosis-inspect pkg/kurtosis/sources/inspect/cmd/main.go -``` - -### Adding New Features - -1. Add functionality to appropriate service layer -2. Create comprehensive tests with real data -3. Update CLI flags if needed -4. Update this README with examples - -## Related Tools - -- **[op-conductor-ops](../../infra/op-conductor-ops/)**: Python CLI for managing conductor clusters -- **[Kurtosis](https://kurtosis.com/)**: Orchestration platform for development environments -- **[Optimism Devnet](../)**: Kurtosis package for Optimism development networks - -## License - -This tool is part of the Optimism monorepo and follows the same licensing terms. \ No newline at end of file diff --git a/kurtosis-devnet/pkg/kurtosis/sources/inspect/cmd/main.go b/kurtosis-devnet/pkg/kurtosis/sources/inspect/cmd/main.go deleted file mode 100644 index 8b4fb2ddab18c..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/inspect/cmd/main.go +++ /dev/null @@ -1,65 +0,0 @@ -// Package main reproduces a lightweight version of the "kurtosis enclave inspect" command -// It can be used to sanity check the results, as writing tests against a fake -// enclave is not practical right now. -package main - -import ( - "context" - "fmt" - "os" - - "github.com/urfave/cli/v2" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/inspect" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/inspect/flags" - opservice "github.com/ethereum-optimism/optimism/op-service" - "github.com/ethereum-optimism/optimism/op-service/cliapp" - oplog "github.com/ethereum-optimism/optimism/op-service/log" -) - -var ( - Version = "v0.1.0" - GitCommit = "" - GitDate = "" -) - -func main() { - app := cli.NewApp() - app.Version = opservice.FormatVersion(Version, GitCommit, GitDate, "") - app.Name = "kurtosis-inspect" - app.Usage = "Inspect Kurtosis enclaves and extract configurations" - app.Description = "Tool to inspect running Kurtosis enclaves and extract conductor configurations and environment data" - app.Flags = cliapp.ProtectFlags(flags.Flags) - app.Action = cliapp.LifecycleCmd(run) - app.ArgsUsage = "" - - if err := app.Run(os.Args); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } -} - -func run(cliCtx *cli.Context, closeApp context.CancelCauseFunc) (cliapp.Lifecycle, error) { - // Parse configuration - cfg, err := inspect.NewConfig(cliCtx) - if err != nil { - return nil, err - } - - // Setup logging - log := oplog.NewLogger(oplog.AppOut(cliCtx), oplog.ReadCLIConfig(cliCtx)) - oplog.SetGlobalLogHandler(log.Handler()) - - // Create service - service := inspect.NewInspectService(cfg, log) - - // Create background context for operations - ctx := context.Background() - - // Run the service - if err := service.Run(ctx); err != nil { - return nil, err - } - - return nil, nil -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/inspect/conductor.go b/kurtosis-devnet/pkg/kurtosis/sources/inspect/conductor.go deleted file mode 100644 index 2c0538c917424..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/inspect/conductor.go +++ /dev/null @@ -1,155 +0,0 @@ -package inspect - -import ( - "context" - "fmt" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/wrappers" -) - -type ConductorSequencer struct { - RaftAddr string `json:"raft_addr" toml:"raft_addr"` - ConductorRPCURL string `json:"conductor_rpc_url" toml:"conductor_rpc_url"` - NodeRPCURL string `json:"node_rpc_url" toml:"node_rpc_url"` - Voting bool `json:"voting" toml:"voting"` -} - -type ConductorNetwork struct { - Sequencers []string `json:"sequencers" toml:"sequencers"` -} - -type ConductorConfig struct { - Networks map[string]*ConductorNetwork `json:"networks" toml:"networks"` - Sequencers map[string]*ConductorSequencer `json:"sequencers" toml:"sequencers"` -} - -func ExtractConductorConfig(ctx context.Context, enclaveID string) (*ConductorConfig, error) { - kurtosisCtx, err := wrappers.GetDefaultKurtosisContext() - if err != nil { - return nil, fmt.Errorf("failed to get Kurtosis context: %w", err) - } - - enclaveCtx, err := kurtosisCtx.GetEnclave(ctx, enclaveID) - if err != nil { - return nil, fmt.Errorf("failed to get enclave: %w", err) - } - - services, err := enclaveCtx.GetServices() - if err != nil { - return nil, fmt.Errorf("failed to get services: %w", err) - } - - conductorServices := make(map[string]map[string]interface{}) - opNodeServices := make(map[string]map[string]interface{}) - - for svcName := range services { - svcNameStr := string(svcName) - - svcCtx, err := enclaveCtx.GetService(svcNameStr) - if err != nil { - continue - } - - labels := svcCtx.GetLabels() - ports := make(map[string]*descriptors.PortInfo) - - for portName, portSpec := range svcCtx.GetPublicPorts() { - ports[portName] = &descriptors.PortInfo{ - Host: svcCtx.GetMaybePublicIPAddress(), - Port: int(portSpec.GetNumber()), - } - } - - if labels["op.kind"] == "conductor" { - conductorServices[svcNameStr] = map[string]interface{}{ - "labels": labels, - "ports": ports, - } - } - - if labels["op.kind"] == "cl" && labels["op.cl.type"] == "op-node" { - opNodeServices[svcNameStr] = map[string]interface{}{ - "labels": labels, - "ports": ports, - } - } - } - - if len(conductorServices) == 0 { - return nil, nil - } - - networks := make(map[string]*ConductorNetwork) - sequencers := make(map[string]*ConductorSequencer) - - networkSequencers := make(map[string][]string) - - for conductorSvcName, conductorData := range conductorServices { - labels := conductorData["labels"].(map[string]string) - ports := conductorData["ports"].(map[string]*descriptors.PortInfo) - - networkID := labels["op.network.id"] - if networkID == "" { - continue - } - - // Find the network name from service name (e.g., "op-conductor-2151908-op-kurtosis-node0") - parts := strings.Split(conductorSvcName, "-") - var networkName string - if len(parts) >= 4 { - networkName = strings.Join(parts[2:len(parts)-1], "-") - } - if networkName == "" { - networkName = "unknown" - } - - networkSequencers[networkName] = append(networkSequencers[networkName], conductorSvcName) - - participantName := labels["op.network.participant.name"] - var nodeRPCURL string - - // Look for matching op-node service - for _, nodeData := range opNodeServices { - nodeLabels := nodeData["labels"].(map[string]string) - nodePorts := nodeData["ports"].(map[string]*descriptors.PortInfo) - - if nodeLabels["op.network.participant.name"] == participantName && - nodeLabels["op.network.id"] == networkID { - if rpcPort, ok := nodePorts["rpc"]; ok { - nodeRPCURL = fmt.Sprintf("http://127.0.0.1:%d", rpcPort.Port) - } - break - } - } - - var raftAddr, conductorRPCURL string - - if consensusPort, ok := ports["consensus"]; ok { - raftAddr = fmt.Sprintf("127.0.0.1:%d", consensusPort.Port) - } - - if rpcPort, ok := ports["rpc"]; ok { - conductorRPCURL = fmt.Sprintf("http://127.0.0.1:%d", rpcPort.Port) - } - - sequencers[conductorSvcName] = &ConductorSequencer{ - RaftAddr: raftAddr, - ConductorRPCURL: conductorRPCURL, - NodeRPCURL: nodeRPCURL, - Voting: true, - } - } - - for networkName, sequencerNames := range networkSequencers { - networks[networkName] = &ConductorNetwork{ - Sequencers: sequencerNames, - } - } - - return &ConductorConfig{ - Networks: networks, - Sequencers: sequencers, - }, nil -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/inspect/conductor_test.go b/kurtosis-devnet/pkg/kurtosis/sources/inspect/conductor_test.go deleted file mode 100644 index 1c65207427949..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/inspect/conductor_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package inspect - -import ( - "strings" - "testing" - - "github.com/BurntSushi/toml" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestConductorConfig(t *testing.T) { - config := &ConductorConfig{ - Networks: map[string]*ConductorNetwork{ - "chain0": {Sequencers: []string{"seq0"}}, - "chain1": {Sequencers: []string{"seq1"}}, - }, - Sequencers: map[string]*ConductorSequencer{ - "seq0": { - RaftAddr: "127.0.0.1:8001", - ConductorRPCURL: "http://127.0.0.1:8002", - NodeRPCURL: "http://127.0.0.1:8003", - Voting: true, - }, - "seq1": { - RaftAddr: "127.0.0.1:8011", - ConductorRPCURL: "http://127.0.0.1:8012", - NodeRPCURL: "http://127.0.0.1:8013", - Voting: false, - }, - }, - } - - var buf strings.Builder - err := toml.NewEncoder(&buf).Encode(config) - require.NoError(t, err) - - output := buf.String() - assert.Contains(t, output, "[networks]") - assert.Contains(t, output, "[sequencers]") - assert.Contains(t, output, "voting = true") - assert.Contains(t, output, "voting = false") - - var decoded ConductorConfig - err = toml.Unmarshal([]byte(output), &decoded) - require.NoError(t, err) - assert.Equal(t, config.Networks, decoded.Networks) - assert.Equal(t, config.Sequencers, decoded.Sequencers) -} - -func TestConductorSequencer(t *testing.T) { - seq := &ConductorSequencer{ - RaftAddr: "localhost:8080", - ConductorRPCURL: "http://localhost:9090", - NodeRPCURL: "http://localhost:7070", - Voting: true, - } - - assert.Equal(t, "localhost:8080", seq.RaftAddr) - assert.Equal(t, "http://localhost:9090", seq.ConductorRPCURL) - assert.True(t, seq.Voting) -} - -func TestMultiChainConfig(t *testing.T) { - config := &ConductorConfig{ - Networks: map[string]*ConductorNetwork{ - "chain0": {Sequencers: []string{"seq0", "backup0"}}, - "chain1": {Sequencers: []string{"seq1", "observer1"}}, - }, - Sequencers: map[string]*ConductorSequencer{ - "seq0": {RaftAddr: "127.0.0.1:8001", ConductorRPCURL: "http://127.0.0.1:8002", NodeRPCURL: "http://127.0.0.1:8003", Voting: true}, - "backup0": {RaftAddr: "127.0.0.1:8011", ConductorRPCURL: "http://127.0.0.1:8012", NodeRPCURL: "http://127.0.0.1:8013", Voting: true}, - "seq1": {RaftAddr: "127.0.0.1:8021", ConductorRPCURL: "http://127.0.0.1:8022", NodeRPCURL: "http://127.0.0.1:8023", Voting: true}, - "observer1": {RaftAddr: "127.0.0.1:8031", ConductorRPCURL: "http://127.0.0.1:8032", NodeRPCURL: "http://127.0.0.1:8033", Voting: false}, - }, - } - - assert.Len(t, config.Networks, 2) - assert.Len(t, config.Sequencers, 4) - assert.False(t, config.Sequencers["observer1"].Voting) - - var buf strings.Builder - err := toml.NewEncoder(&buf).Encode(config) - require.NoError(t, err) - assert.Contains(t, buf.String(), "voting = false") -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/inspect/config.go b/kurtosis-devnet/pkg/kurtosis/sources/inspect/config.go deleted file mode 100644 index 935c00e3a9cb9..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/inspect/config.go +++ /dev/null @@ -1,34 +0,0 @@ -package inspect - -import ( - "fmt" - - "github.com/urfave/cli/v2" -) - -// Config holds the configuration for the inspect service -type Config struct { - EnclaveID string - FixTraefik bool - ConductorConfigPath string - EnvironmentPath string -} - -func NewConfig(ctx *cli.Context) (*Config, error) { - if ctx.NArg() != 1 { - return nil, fmt.Errorf("expected exactly one argument (enclave-id), got %d", ctx.NArg()) - } - - cfg := &Config{ - EnclaveID: ctx.Args().Get(0), - FixTraefik: ctx.Bool("fix-traefik"), - ConductorConfigPath: ctx.String("conductor-config-path"), - EnvironmentPath: ctx.String("environment-path"), - } - - if cfg.EnclaveID == "" { - return nil, fmt.Errorf("enclave-id is required") - } - - return cfg, nil -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/inspect/config_test.go b/kurtosis-devnet/pkg/kurtosis/sources/inspect/config_test.go deleted file mode 100644 index 25dae574ff07e..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/inspect/config_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package inspect - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/urfave/cli/v2" -) - -func TestNewConfig(t *testing.T) { - tests := []struct { - name string - args []string - expected *Config - wantErr bool - }{ - { - name: "valid config", - args: []string{"inspect", "test-enclave"}, - expected: &Config{ - EnclaveID: "test-enclave", - FixTraefik: false, - ConductorConfigPath: "", - EnvironmentPath: "", - }, - wantErr: false, - }, - { - name: "config with flags", - args: []string{ - "inspect", - "--fix-traefik", - "--conductor-config-path", "/tmp/conductor.toml", - "--environment-path", "/tmp/env.json", - "my-enclave", - }, - expected: &Config{ - EnclaveID: "my-enclave", - FixTraefik: true, - ConductorConfigPath: "/tmp/conductor.toml", - EnvironmentPath: "/tmp/env.json", - }, - wantErr: false, - }, - { - name: "no arguments", - args: []string{"inspect"}, - expected: nil, - wantErr: true, - }, - { - name: "too many arguments", - args: []string{"inspect", "enclave1", "enclave2"}, - expected: nil, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - app := &cli.App{ - Name: "inspect", - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "fix-traefik"}, - &cli.StringFlag{Name: "conductor-config-path"}, - &cli.StringFlag{Name: "environment-path"}, - }, - Action: func(ctx *cli.Context) error { - cfg, err := NewConfig(ctx) - - if tt.wantErr { - assert.Error(t, err) - assert.Nil(t, cfg) - } else { - require.NoError(t, err) - require.NotNil(t, cfg) - assert.Equal(t, tt.expected.EnclaveID, cfg.EnclaveID) - assert.Equal(t, tt.expected.FixTraefik, cfg.FixTraefik) - assert.Equal(t, tt.expected.ConductorConfigPath, cfg.ConductorConfigPath) - assert.Equal(t, tt.expected.EnvironmentPath, cfg.EnvironmentPath) - } - return nil - }, - } - - err := app.Run(tt.args) - require.NoError(t, err) - }) - } -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/inspect/flags/flags.go b/kurtosis-devnet/pkg/kurtosis/sources/inspect/flags/flags.go deleted file mode 100644 index 22c6b364984f7..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/inspect/flags/flags.go +++ /dev/null @@ -1,50 +0,0 @@ -package flags - -import ( - "github.com/urfave/cli/v2" - - opservice "github.com/ethereum-optimism/optimism/op-service" - oplog "github.com/ethereum-optimism/optimism/op-service/log" -) - -const EnvVarPrefix = "KURTOSIS_INSPECT" - -var ( - FixTraefik = &cli.BoolFlag{ - Name: "fix-traefik", - Value: false, - EnvVars: opservice.PrefixEnvVar(EnvVarPrefix, "FIX_TRAEFIK"), - Usage: "Fix missing Traefik labels on containers", - } - ConductorConfig = &cli.StringFlag{ - Name: "conductor-config-path", - Value: "", - EnvVars: opservice.PrefixEnvVar(EnvVarPrefix, "CONDUCTOR_CONFIG"), - Usage: "Path where conductor configuration TOML file will be written (overwrites existing file)", - } - Environment = &cli.StringFlag{ - Name: "environment-path", - Value: "", - EnvVars: opservice.PrefixEnvVar(EnvVarPrefix, "ENVIRONMENT"), - Usage: "Path where environment JSON file will be written (overwrites existing file)", - } -) - -var requiredFlags = []cli.Flag{ - // No required flags -} - -var optionalFlags = []cli.Flag{ - FixTraefik, - ConductorConfig, - Environment, -} - -var Flags []cli.Flag - -func init() { - // Add common op-service flags - optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...) - - Flags = append(requiredFlags, optionalFlags...) -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/inspect/flags/flags_test.go b/kurtosis-devnet/pkg/kurtosis/sources/inspect/flags/flags_test.go deleted file mode 100644 index 8c9d85d29b1ca..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/inspect/flags/flags_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package flags - -import ( - "os" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/urfave/cli/v2" -) - -func TestFlags(t *testing.T) { - tests := []struct { - name string - args []string - envVars map[string]string - expected struct { - fixTraefik bool - conductorConfig string - environment string - } - }{ - { - name: "default values", - args: []string{"inspect", "test-enclave"}, - expected: struct { - fixTraefik bool - conductorConfig string - environment string - }{ - fixTraefik: false, - conductorConfig: "", - environment: "", - }, - }, - { - name: "cli flags set", - args: []string{ - "inspect", - "--fix-traefik", - "--conductor-config-path", "/tmp/conductor.toml", - "--environment-path", "/tmp/env.json", - "test-enclave", - }, - expected: struct { - fixTraefik bool - conductorConfig string - environment string - }{ - fixTraefik: true, - conductorConfig: "/tmp/conductor.toml", - environment: "/tmp/env.json", - }, - }, - { - name: "environment variables", - args: []string{"inspect", "test-enclave"}, - envVars: map[string]string{ - "KURTOSIS_INSPECT_FIX_TRAEFIK": "true", - "KURTOSIS_INSPECT_CONDUCTOR_CONFIG": "/env/conductor.toml", - "KURTOSIS_INSPECT_ENVIRONMENT": "/env/env.json", - }, - expected: struct { - fixTraefik bool - conductorConfig string - environment string - }{ - fixTraefik: true, - conductorConfig: "/env/conductor.toml", - environment: "/env/env.json", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Set environment variables - for key, value := range tt.envVars { - os.Setenv(key, value) - defer os.Unsetenv(key) - } - - app := &cli.App{ - Name: "inspect", - Flags: Flags, - Action: func(ctx *cli.Context) error { - assert.Equal(t, tt.expected.fixTraefik, ctx.Bool("fix-traefik")) - assert.Equal(t, tt.expected.conductorConfig, ctx.String("conductor-config-path")) - assert.Equal(t, tt.expected.environment, ctx.String("environment-path")) - return nil - }, - } - - err := app.Run(tt.args) - require.NoError(t, err) - }) - } -} - -func TestFlagDefinitions(t *testing.T) { - flagNames := make(map[string]bool) - for _, flag := range Flags { - for _, name := range flag.Names() { - flagNames[name] = true - } - } - - assert.True(t, flagNames["fix-traefik"]) - assert.True(t, flagNames["conductor-config-path"]) - assert.True(t, flagNames["environment-path"]) - assert.True(t, flagNames["log.level"]) -} - -func TestEnvVarPrefix(t *testing.T) { - assert.Equal(t, "KURTOSIS_INSPECT", EnvVarPrefix) -} - -func TestFlagStructure(t *testing.T) { - assert.NotEmpty(t, Flags) - assert.Contains(t, optionalFlags, FixTraefik) - assert.Contains(t, optionalFlags, ConductorConfig) - assert.Contains(t, optionalFlags, Environment) - assert.Empty(t, requiredFlags) -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/inspect/inspect.go b/kurtosis-devnet/pkg/kurtosis/sources/inspect/inspect.go deleted file mode 100644 index 286fd9cfc6031..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/inspect/inspect.go +++ /dev/null @@ -1,116 +0,0 @@ -package inspect - -import ( - "context" - "fmt" - "net/http" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/wrappers" -) - -type PortMap map[string]*descriptors.PortInfo - -type Service struct { - Labels map[string]string - Ports PortMap -} - -type ServiceMap map[string]*Service - -// InspectData represents a summary of the output of "kurtosis enclave inspect" -type InspectData struct { - FileArtifacts []string - UserServices ServiceMap -} - -type Inspector struct { - enclaveID string -} - -func NewInspector(enclaveID string) *Inspector { - return &Inspector{enclaveID: enclaveID} -} - -func ShortenedUUIDString(fullUUID string) string { - lengthToTrim := 12 - if lengthToTrim > len(fullUUID) { - lengthToTrim = len(fullUUID) - } - return fullUUID[:lengthToTrim] -} - -func (e *Inspector) ExtractData(ctx context.Context) (*InspectData, error) { - kurtosisCtx, err := wrappers.GetDefaultKurtosisContext() - if err != nil { - return nil, err - } - - enclaveCtx, err := kurtosisCtx.GetEnclave(ctx, e.enclaveID) - if err != nil { - return nil, err - } - - services, err := enclaveCtx.GetServices() - if err != nil { - return nil, err - } - - artifacts, err := enclaveCtx.GetAllFilesArtifactNamesAndUuids(ctx) - if err != nil { - return nil, err - } - - enclaveUUID := string(enclaveCtx.GetEnclaveUuid()) - - data := &InspectData{ - UserServices: make(ServiceMap), - FileArtifacts: make([]string, len(artifacts)), - } - - for i, artifact := range artifacts { - data.FileArtifacts[i] = artifact.GetFileName() - } - - for svc := range services { - svc := string(svc) - svcCtx, err := enclaveCtx.GetService(svc) - if err != nil { - return nil, err - } - svcUUID := string(svcCtx.GetServiceUUID()) - - portMap := make(PortMap) - - for port, portSpec := range svcCtx.GetPublicPorts() { - portMap[port] = &descriptors.PortInfo{ - Host: svcCtx.GetMaybePublicIPAddress(), - Port: int(portSpec.GetNumber()), - } - } - shortEnclaveUuid := ShortenedUUIDString(enclaveUUID) - shortServiceUuid := ShortenedUUIDString(svcUUID) - for port, portSpec := range svcCtx.GetPrivatePorts() { - // avoid non-mapped ports, we shouldn't have to use them. - if p, ok := portMap[port]; ok { - p.PrivatePort = int(portSpec.GetNumber()) - p.ReverseProxyHeader = http.Header{ - // This allows going through the kurtosis reverse proxy for each port - "Host": []string{fmt.Sprintf("%d-%s-%s", p.PrivatePort, shortServiceUuid, shortEnclaveUuid)}, - } - - portMap[port] = p - } - } - - if len(portMap) != 0 { - data.UserServices[svc] = &Service{ - Ports: portMap, - Labels: svcCtx.GetLabels(), - } - } - - } - - return data, nil -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/inspect/inspect_test.go b/kurtosis-devnet/pkg/kurtosis/sources/inspect/inspect_test.go deleted file mode 100644 index 645f952edce1e..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/inspect/inspect_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package inspect - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" -) - -func TestNewInspector(t *testing.T) { - inspector := NewInspector("test-enclave") - assert.NotNil(t, inspector) - assert.Equal(t, "test-enclave", inspector.enclaveID) -} - -func TestShortenedUUIDString(t *testing.T) { - assert.Equal(t, "f47ac10b-58c", ShortenedUUIDString("f47ac10b-58cc-4372-a567-0e02b2c3d479")) - assert.Equal(t, "abc", ShortenedUUIDString("abc")) - assert.Equal(t, "", ShortenedUUIDString("")) - assert.Equal(t, "123456789012", ShortenedUUIDString("123456789012")) - assert.Equal(t, "test2-devnet", ShortenedUUIDString("test2-devnet-2151908")) -} - -func TestInspectData(t *testing.T) { - data := &InspectData{ - FileArtifacts: []string{"genesis.json", "jwt.txt"}, - UserServices: ServiceMap{ - "op-node": &Service{ - Labels: map[string]string{"app": "op-node", "role": "sequencer"}, - Ports: PortMap{ - "rpc": &descriptors.PortInfo{Host: "127.0.0.1", Port: 8545}, - "p2p": &descriptors.PortInfo{Host: "127.0.0.1", Port: 9222}, - }, - }, - }, - } - - assert.Len(t, data.FileArtifacts, 2) - assert.Len(t, data.UserServices, 1) - assert.Contains(t, data.FileArtifacts, "genesis.json") - - service := data.UserServices["op-node"] - assert.Equal(t, "op-node", service.Labels["app"]) - assert.Equal(t, "sequencer", service.Labels["role"]) - - rpcPort, exists := service.Ports["rpc"] - require.True(t, exists) - assert.Equal(t, 8545, rpcPort.Port) - - _, exists = service.Ports["nonexistent"] - assert.False(t, exists) -} - -func TestServiceMap(t *testing.T) { - services := ServiceMap{ - "seq0": &Service{Labels: map[string]string{"role": "sequencer"}, Ports: PortMap{"rpc": &descriptors.PortInfo{Port: 8545}}}, - "seq1": &Service{Labels: map[string]string{"role": "sequencer"}, Ports: PortMap{"rpc": &descriptors.PortInfo{Port: 8645}}}, - "conductor": &Service{Labels: map[string]string{"app": "conductor"}, Ports: PortMap{"rpc": &descriptors.PortInfo{Port: 8547}}}, - } - - assert.Len(t, services, 3) - - seq0, exists := services["seq0"] - require.True(t, exists) - assert.Equal(t, "sequencer", seq0.Labels["role"]) - - sequencerCount := 0 - for _, svc := range services { - if svc.Labels["role"] == "sequencer" { - sequencerCount++ - } - } - assert.Equal(t, 2, sequencerCount) -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/inspect/service.go b/kurtosis-devnet/pkg/kurtosis/sources/inspect/service.go deleted file mode 100644 index 099e7c62ccf49..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/inspect/service.go +++ /dev/null @@ -1,150 +0,0 @@ -package inspect - -import ( - "context" - "encoding/json" - "fmt" - "os" - - "github.com/BurntSushi/toml" - "github.com/ethereum/go-ethereum/log" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/util" -) - -// InspectService handles the core inspection functionality -type InspectService struct { - cfg *Config - log log.Logger -} - -func NewInspectService(cfg *Config, log log.Logger) *InspectService { - return &InspectService{ - cfg: cfg, - log: log, - } -} - -func (s *InspectService) Run(ctx context.Context) error { - if s.cfg.FixTraefik { - return s.fixTraefik(ctx) - } - - return s.inspect(ctx) -} - -func (s *InspectService) fixTraefik(ctx context.Context) error { - s.log.Info("Fixing Traefik network configuration...") - fmt.Println("🔧 Fixing Traefik network configuration...") - - if err := util.SetReverseProxyConfig(ctx); err != nil { - return fmt.Errorf("error setting reverse proxy config: %w", err) - } - - s.log.Info("Traefik network configuration fixed") - fmt.Println("✅ Traefik network configuration fixed!") - return nil -} - -func (s *InspectService) inspect(ctx context.Context) error { - inspector := NewInspector(s.cfg.EnclaveID) - - data, err := inspector.ExtractData(ctx) - if err != nil { - return fmt.Errorf("error inspecting enclave: %w", err) - } - - conductorConfig, err := ExtractConductorConfig(ctx, s.cfg.EnclaveID) - if err != nil { - s.log.Warn("Error extracting conductor configuration", "error", err) - } - - s.displayResults(data, conductorConfig) - - if err := s.writeFiles(data, conductorConfig); err != nil { - return fmt.Errorf("error writing output files: %w", err) - } - - return nil -} - -func (s *InspectService) displayResults(data *InspectData, conductorConfig *ConductorConfig) { - fmt.Println("File Artifacts:") - for _, artifact := range data.FileArtifacts { - fmt.Printf(" %s\n", artifact) - } - - fmt.Println("\nServices:") - for name, svc := range data.UserServices { - fmt.Printf(" %s:\n", name) - for portName, portInfo := range svc.Ports { - host := portInfo.Host - if host == "" { - host = "localhost" - } - fmt.Printf(" %s: %s:%d\n", portName, host, portInfo.Port) - } - } - - if conductorConfig != nil { - fmt.Println("\nConductor Configuration:") - fmt.Println("========================") - - if err := toml.NewEncoder(os.Stdout).Encode(conductorConfig); err != nil { - s.log.Error("Error marshaling conductor config to TOML", "error", err) - } - } -} - -func (s *InspectService) writeFiles(data *InspectData, conductorConfig *ConductorConfig) error { - if s.cfg.ConductorConfigPath != "" { - if conductorConfig == nil { - s.log.Info("No conductor services found, skipping conductor config generation") - } else { - if err := s.writeConductorConfig(s.cfg.ConductorConfigPath, conductorConfig); err != nil { - return fmt.Errorf("error writing conductor config file: %w", err) - } - fmt.Printf("Conductor configuration saved to: %s\n", s.cfg.ConductorConfigPath) - } - } - - if s.cfg.EnvironmentPath != "" { - if err := s.writeEnvironment(s.cfg.EnvironmentPath, data); err != nil { - return fmt.Errorf("error writing environment file: %w", err) - } - fmt.Printf("Environment data saved to: %s\n", s.cfg.EnvironmentPath) - } - - return nil -} - -func (s *InspectService) writeConductorConfig(path string, config *ConductorConfig) error { - out, err := os.Create(path) - if err != nil { - return fmt.Errorf("error creating conductor config file: %w", err) - } - defer out.Close() - - encoder := toml.NewEncoder(out) - if err := encoder.Encode(config); err != nil { - return fmt.Errorf("error encoding conductor config as TOML: %w", err) - } - - return nil -} - -func (s *InspectService) writeEnvironment(path string, data *InspectData) error { - out, err := os.Create(path) - if err != nil { - return fmt.Errorf("error creating environment file: %w", err) - } - defer out.Close() - - enc := json.NewEncoder(out) - enc.SetIndent("", " ") - if err := enc.Encode(data); err != nil { - return fmt.Errorf("error encoding environment: %w", err) - } - - return nil -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/inspect/service_test.go b/kurtosis-devnet/pkg/kurtosis/sources/inspect/service_test.go deleted file mode 100644 index afa4590388f2c..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/inspect/service_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package inspect - -import ( - "os" - "path/filepath" - "testing" - - "github.com/ethereum/go-ethereum/log" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" -) - -func TestInspectService(t *testing.T) { - cfg := &Config{EnclaveID: "test-enclave"} - service := NewInspectService(cfg, log.New()) - - assert.NotNil(t, service) - assert.Equal(t, cfg, service.cfg) -} - -func TestFileWriting(t *testing.T) { - tempDir := t.TempDir() - - cfg := &Config{ - EnclaveID: "test-enclave", - ConductorConfigPath: filepath.Join(tempDir, "conductor.toml"), - EnvironmentPath: filepath.Join(tempDir, "environment.json"), - } - service := NewInspectService(cfg, log.New()) - - conductorConfig := &ConductorConfig{ - Networks: map[string]*ConductorNetwork{"chain": {Sequencers: []string{"seq"}}}, - Sequencers: map[string]*ConductorSequencer{"seq": {RaftAddr: "127.0.0.1:8001", ConductorRPCURL: "http://127.0.0.1:8002", NodeRPCURL: "http://127.0.0.1:8003", Voting: true}}, - } - - inspectData := &InspectData{ - FileArtifacts: []string{"genesis.json", "jwt.txt"}, - UserServices: ServiceMap{ - "op-node": &Service{ - Labels: map[string]string{"app": "op-node"}, - Ports: PortMap{"rpc": &descriptors.PortInfo{Host: "127.0.0.1", Port: 8545}}, - }, - }, - } - - err := service.writeFiles(inspectData, conductorConfig) - require.NoError(t, err) - - assert.FileExists(t, cfg.ConductorConfigPath) - assert.FileExists(t, cfg.EnvironmentPath) - - content, err := os.ReadFile(cfg.ConductorConfigPath) - require.NoError(t, err) - assert.Contains(t, string(content), "[networks]") - assert.Contains(t, string(content), "[sequencers]") - - envContent, err := os.ReadFile(cfg.EnvironmentPath) - require.NoError(t, err) - assert.Contains(t, string(envContent), "genesis.json") - assert.Contains(t, string(envContent), "op-node") -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/interfaces/interfaces.go b/kurtosis-devnet/pkg/kurtosis/sources/interfaces/interfaces.go deleted file mode 100644 index 269226ed9f7a0..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/interfaces/interfaces.go +++ /dev/null @@ -1,32 +0,0 @@ -package interfaces - -import ( - "context" - "io" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/deployer" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/inspect" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/jwt" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" -) - -type EnclaveSpecifier interface { - EnclaveSpec(io.Reader) (*spec.EnclaveSpec, error) -} - -type EnclaveInspecter interface { - EnclaveInspect(context.Context, string) (*inspect.InspectData, error) -} - -type EnclaveObserver interface { - EnclaveObserve(context.Context, string) (*deployer.DeployerData, error) -} - -type JWTExtractor interface { - ExtractData(context.Context, string) (*jwt.Data, error) -} - -type DepsetExtractor interface { - ExtractData(context.Context, string) (map[string]descriptors.DepSet, error) -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/jwt/cmd/main.go b/kurtosis-devnet/pkg/kurtosis/sources/jwt/cmd/main.go deleted file mode 100644 index fd71974554bd0..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/jwt/cmd/main.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/jwt" - "github.com/ethereum-optimism/optimism/op-service/cliapp" - "github.com/urfave/cli/v2" -) - -var ( - GitCommit = "" - GitDate = "" -) - -func main() { - app := cli.NewApp() - app.Version = fmt.Sprintf("%s-%s", GitCommit, GitDate) - app.Name = "jwt" - app.Usage = "Tool to extract JWT secrets from Kurtosis enclaves" - app.Flags = cliapp.ProtectFlags([]cli.Flag{ - &cli.StringFlag{ - Name: "enclave", - Usage: "Name of the Kurtosis enclave", - Required: true, - }, - }) - app.Action = runJWT - app.Writer = os.Stdout - app.ErrWriter = os.Stderr - - err := app.Run(os.Args) - if err != nil { - _, _ = fmt.Fprintf(os.Stderr, "Application failed: %v\n", err) - os.Exit(1) - } -} - -func runJWT(ctx *cli.Context) error { - enclave := ctx.String("enclave") - - extractor := jwt.NewExtractor(enclave) - data, err := extractor.ExtractData(ctx.Context) - if err != nil { - return fmt.Errorf("failed to extract JWT data: %w", err) - } - - // Print the JWT secrets - fmt.Printf("L1 JWT Secret: %s\n", data.L1JWT) - fmt.Printf("L2 JWT Secret: %s\n", data.L2JWT) - - return nil -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/jwt/jwt.go b/kurtosis-devnet/pkg/kurtosis/sources/jwt/jwt.go deleted file mode 100644 index 20e950258fd27..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/jwt/jwt.go +++ /dev/null @@ -1,87 +0,0 @@ -package jwt - -import ( - "bytes" - "context" - "fmt" - "io" - - ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/util" -) - -const ( - jwtSecretFileName = "jwtsecret" -) - -// Data holds the JWT secrets for L1 and L2 -type Data struct { - L1JWT string - L2JWT string -} - -// extractor implements the interfaces.JWTExtractor interface -type extractor struct { - enclave string -} - -// NewExtractor creates a new JWT extractor -func NewExtractor(enclave string) *extractor { - return &extractor{ - enclave: enclave, - } -} - -// ExtractData extracts JWT secrets from their respective artifacts -func (e *extractor) ExtractData(ctx context.Context) (*Data, error) { - fs, err := ktfs.NewEnclaveFS(ctx, e.enclave) - if err != nil { - return nil, err - } - - // Get L1 JWT with retry logic - l1JWT, err := util.WithRetry(ctx, "ExtractL1JWT", func() (string, error) { - return extractJWTFromArtifact(ctx, fs, "jwt_file") - }) - if err != nil { - return nil, fmt.Errorf("failed to get L1 JWT: %w", err) - } - - // Get L2 JWT with retry logic - l2JWT, err := util.WithRetry(ctx, "ExtractL2JWT", func() (string, error) { - return extractJWTFromArtifact(ctx, fs, "op_jwt_file") - }) - if err != nil { - return nil, fmt.Errorf("failed to get L2 JWT: %w", err) - } - - return &Data{ - L1JWT: l1JWT, - L2JWT: l2JWT, - }, nil -} - -func extractJWTFromArtifact(ctx context.Context, fs *ktfs.EnclaveFS, artifactName string) (string, error) { - // Get artifact with retry logic - a, err := util.WithRetry(ctx, fmt.Sprintf("GetArtifact(%s)", artifactName), func() (*ktfs.Artifact, error) { - return fs.GetArtifact(ctx, artifactName) - }) - if err != nil { - return "", fmt.Errorf("failed to get artifact: %w", err) - } - - buffer := &bytes.Buffer{} - if err := a.ExtractFiles(ktfs.NewArtifactFileWriter(jwtSecretFileName, buffer)); err != nil { - return "", fmt.Errorf("failed to extract JWT: %w", err) - } - - return parseJWT(buffer) -} - -func parseJWT(r io.Reader) (string, error) { - data, err := io.ReadAll(r) - if err != nil { - return "", fmt.Errorf("failed to read JWT file: %w", err) - } - return string(data), nil -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/spec/spec.go b/kurtosis-devnet/pkg/kurtosis/sources/spec/spec.go deleted file mode 100644 index f3f94058205c3..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/spec/spec.go +++ /dev/null @@ -1,172 +0,0 @@ -package spec - -import ( - "fmt" - "io" - - "gopkg.in/yaml.v3" -) - -const ( - FeatureInterop = "interop" - FeatureFaucet = "faucet" -) - -// ChainSpec represents the network parameters for a chain -type ChainSpec struct { - Name string - NetworkID string - Nodes map[string]NodeConfig -} - -// NodeConfig represents the configuration for a chain node -type NodeConfig struct { - IsSequencer bool - ELType string - CLType string -} - -type FeatureList []string - -func (fl FeatureList) Contains(feature string) bool { - for _, f := range fl { - if f == feature { - return true - } - } - return false -} - -// EnclaveSpec represents the parsed chain specifications from the YAML -type EnclaveSpec struct { - Chains []*ChainSpec - Features FeatureList -} - -// NetworkParams represents the network parameters section in the YAML -type NetworkParams struct { - Name string `yaml:"name"` - NetworkID string `yaml:"network_id"` -} - -// ChainConfig represents a chain configuration in the YAML -type ChainConfig struct { - NetworkParams NetworkParams `yaml:"network_params"` - Participants map[string]ParticipantConfig `yaml:"participants"` -} - -// NodeConfig represents a node configuration in the YAML -type ParticipantConfig struct { - Sequencer bool `yaml:"sequencer"` - EL ComponentType `yaml:"el"` - CL ComponentType `yaml:"cl"` -} - -// ComponentType represents a component type in the YAML -type ComponentType struct { - Type string `yaml:"type"` -} - -// InteropConfig represents the interop section in the YAML -type SuperchainConfig struct { - Enabled bool `yaml:"enabled"` -} - -// FaucetConfig represents the faucet section in the YAML -type FaucetConfig struct { - Enabled bool `yaml:"enabled"` -} - -// OptimismPackage represents the optimism_package section in the YAML -type OptimismPackage struct { - Faucet FaucetConfig `yaml:"faucet"` - Superchains map[string]SuperchainConfig `yaml:"superchains"` - Chains map[string]ChainConfig `yaml:"chains"` -} - -// YAMLSpec represents the root of the YAML document -type YAMLSpec struct { - OptimismPackage OptimismPackage `yaml:"optimism_package"` -} - -type Spec struct{} - -type SpecOption func(*Spec) - -func NewSpec(opts ...SpecOption) *Spec { - s := &Spec{} - for _, opt := range opts { - opt(s) - } - return s -} - -type featureExtractor func(YAMLSpec, string) bool - -var featuresMap = map[string]featureExtractor{ - FeatureInterop: interopExtractor, - FeatureFaucet: faucetExtractor, -} - -func interopExtractor(yamlSpec YAMLSpec, _feature string) bool { - for _, superchain := range yamlSpec.OptimismPackage.Superchains { - if superchain.Enabled { - return true - } - } - return false -} - -func faucetExtractor(yamlSpec YAMLSpec, _feature string) bool { - return yamlSpec.OptimismPackage.Faucet.Enabled -} - -// ExtractData parses a YAML document and returns the chain specifications -func (s *Spec) ExtractData(r io.Reader) (*EnclaveSpec, error) { - var yamlSpec YAMLSpec - decoder := yaml.NewDecoder(r) - if err := decoder.Decode(&yamlSpec); err != nil { - return nil, fmt.Errorf("failed to decode YAML: %w", err) - } - - var features []string - for feature, extractor := range featuresMap { - if extractor(yamlSpec, feature) { - features = append(features, feature) - } - } - - result := &EnclaveSpec{ - Chains: make([]*ChainSpec, 0, len(yamlSpec.OptimismPackage.Chains)), - Features: features, - } - - // Extract chain specifications - for name, chain := range yamlSpec.OptimismPackage.Chains { - - nodes := make(map[string]NodeConfig, len(chain.Participants)) - for name, participant := range chain.Participants { - elType := participant.EL.Type - clType := participant.CL.Type - if elType == "" { - elType = "op-geth" - } - if clType == "" { - clType = "op-node" - } - nodes[name] = NodeConfig{ - IsSequencer: participant.Sequencer, - ELType: elType, - CLType: clType, - } - } - - result.Chains = append(result.Chains, &ChainSpec{ - Name: name, - NetworkID: chain.NetworkParams.NetworkID, - Nodes: nodes, - }) - } - - return result, nil -} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/spec/spec_test.go b/kurtosis-devnet/pkg/kurtosis/sources/spec/spec_test.go deleted file mode 100644 index 6988034d96dcc..0000000000000 --- a/kurtosis-devnet/pkg/kurtosis/sources/spec/spec_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package spec - -import ( - "sort" - "strings" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestParseSpec(t *testing.T) { - yamlContent := ` -optimism_package: - chains: - op-rollup-one: - participants: - node0: - el: - type: op-geth - network_params: - network_id: "3151909" - blockscout_params: - enabled: true - op-rollup-two: - participants: - node0: - el: - type: op-geth - network_params: - network_id: "3151910" -ethereum_package: - participants: - - el_type: geth - - el_type: reth - network_params: - preset: minimal - genesis_delay: 5 -` - - result, err := NewSpec().ExtractData(strings.NewReader(yamlContent)) - require.NoError(t, err) - - expectedChains := []ChainSpec{ - { - Name: "op-rollup-one", - NetworkID: "3151909", - }, - { - Name: "op-rollup-two", - NetworkID: "3151910", - }, - } - - require.Len(t, result.Chains, len(expectedChains)) - sort.Slice(result.Chains, func(i, j int) bool { - return result.Chains[i].Name < result.Chains[j].Name - }) - - for i, expected := range expectedChains { - actual := result.Chains[i] - require.Equal(t, expected.Name, actual.Name, "Chain %d: name mismatch", i) - require.Equal(t, expected.NetworkID, actual.NetworkID, "Chain %d: network ID mismatch", i) - } -} - -func TestParseSpecErrors(t *testing.T) { - tests := []struct { - name string - yaml string - wantErr bool - }{ - { - name: "empty yaml", - yaml: "", - wantErr: true, - }, - { - name: "invalid yaml", - yaml: "invalid: [yaml: content", - wantErr: true, - }, - { - name: "missing network params", - yaml: ` -optimism_package: - chains: - op-kurtosis: - participants: - node0: - el: - type: op-geth - blockscout_params: - enabled: true`, - }, - { - name: "missing chains", - yaml: ` -optimism_package: - other_field: value`, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, err := NewSpec().ExtractData(strings.NewReader(tt.yaml)) - if tt.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/kurtosis-devnet/pkg/tmpl/cmd/main.go b/kurtosis-devnet/pkg/tmpl/cmd/main.go deleted file mode 100644 index a64d08bc7f773..0000000000000 --- a/kurtosis-devnet/pkg/tmpl/cmd/main.go +++ /dev/null @@ -1,69 +0,0 @@ -package main - -import ( - "encoding/json" - "flag" - "fmt" - "os" - "strings" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/tmpl" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/tmpl/fake" -) - -func main() { - // Parse command line flags - templateFile := flag.String("template", "", "Path to template file") - dataFile := flag.String("data", "", "Optional JSON data file") - flag.Parse() - - if *templateFile == "" { - fmt.Fprintln(os.Stderr, "Error: --template flag is required") - flag.Usage() - os.Exit(1) - } - - // Open template file - f, err := os.Open(*templateFile) - if err != nil { - fmt.Fprintf(os.Stderr, "Error opening template file: %v\n", err) - os.Exit(1) - } - defer f.Close() - - // Get basename of template file without extension - base := *templateFile - if lastSlash := strings.LastIndex(base, "/"); lastSlash >= 0 { - base = base[lastSlash+1:] - } - if lastDot := strings.LastIndex(base, "."); lastDot >= 0 { - base = base[:lastDot] - } - enclave := base + "-devnet" - - // Create template context - ctx := fake.NewFakeTemplateContext(enclave) - - // Load data file if provided - if *dataFile != "" { - dataBytes, err := os.ReadFile(*dataFile) - if err != nil { - fmt.Fprintf(os.Stderr, "Error reading data file: %v\n", err) - os.Exit(1) - } - - var data interface{} - if err := json.Unmarshal(dataBytes, &data); err != nil { - fmt.Fprintf(os.Stderr, "Error parsing data file as JSON: %v\n", err) - os.Exit(1) - } - - tmpl.WithData(data)(ctx) - } - - // Process template and write to stdout - if err := ctx.InstantiateTemplate(f, os.Stdout); err != nil { - fmt.Fprintf(os.Stderr, "Error processing template: %v\n", err) - os.Exit(1) - } -} diff --git a/kurtosis-devnet/pkg/tmpl/fake/fake.go b/kurtosis-devnet/pkg/tmpl/fake/fake.go deleted file mode 100644 index 3ad9c36ebfe8c..0000000000000 --- a/kurtosis-devnet/pkg/tmpl/fake/fake.go +++ /dev/null @@ -1,31 +0,0 @@ -package fake - -import ( - "fmt" - - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/tmpl" -) - -type PrestateInfo struct { - URL string `json:"url"` - Hashes map[string]string `json:"hashes"` -} - -func NewFakeTemplateContext(enclave string) *tmpl.TemplateContext { - return tmpl.NewTemplateContext( - tmpl.WithFunction("localDockerImage", func(image string) (string, error) { - return fmt.Sprintf("%s:%s", image, enclave), nil - }), - tmpl.WithFunction("localContractArtifacts", func(layer string) (string, error) { - return fmt.Sprintf("http://host.docker.internal:0/contracts-bundle-%s.tar.gz", enclave), nil - }), - tmpl.WithFunction("localPrestate", func() (*PrestateInfo, error) { - return &PrestateInfo{ - URL: "http://fileserver/proofs/op-program/cannon", - Hashes: map[string]string{ - "prestate": "0x1234567890", - }, - }, nil - }), - ) -} diff --git a/kurtosis-devnet/pkg/tmpl/tmpl.go b/kurtosis-devnet/pkg/tmpl/tmpl.go deleted file mode 100644 index ab931063f9796..0000000000000 --- a/kurtosis-devnet/pkg/tmpl/tmpl.go +++ /dev/null @@ -1,189 +0,0 @@ -package tmpl - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "os" - "path/filepath" - "text/template" - - sprig "github.com/go-task/slim-sprig/v3" - "gopkg.in/yaml.v3" -) - -// TemplateFunc represents a function that can be used in templates -type TemplateFunc any - -// TemplateContext contains data and functions to be passed to templates -type TemplateContext struct { - baseDir string - Data interface{} - Functions map[string]TemplateFunc - includeStack []string // Track stack of included files to detect circular includes -} - -type TemplateContextOptions func(*TemplateContext) - -func WithBaseDir(basedir string) TemplateContextOptions { - return func(ctx *TemplateContext) { - ctx.baseDir = basedir - } -} - -func WithFunction(name string, fn TemplateFunc) TemplateContextOptions { - return func(ctx *TemplateContext) { - ctx.Functions[name] = fn - } -} - -func WithData(data interface{}) TemplateContextOptions { - return func(ctx *TemplateContext) { - ctx.Data = data - } -} - -// NewTemplateContext creates a new TemplateContext with default functions -func NewTemplateContext(opts ...TemplateContextOptions) *TemplateContext { - ctx := &TemplateContext{ - baseDir: ".", - Functions: make(map[string]TemplateFunc), - includeStack: make([]string, 0), - } - - for _, opt := range opts { - opt(ctx) - } - - return ctx -} - -// includeFile reads and processes a template file relative to the given context's baseDir, -// parses the content as YAML, and returns its JSON representation. -// We use JSON because it can be inlined without worrying about indentation, while remaining valid YAML. -// Note: to protect against infinite recursion, we check for circular includes. -func (ctx *TemplateContext) includeFile(fname string, data ...interface{}) (string, error) { - // Resolve the file path relative to baseDir - path := filepath.Join(ctx.baseDir, fname) - - // Check for circular includes - absPath, err := filepath.Abs(path) - if err != nil { - return "", fmt.Errorf("error resolving absolute path: %w", err) - } - for _, includedFile := range ctx.includeStack { - if includedFile == absPath { - return "", fmt.Errorf("circular include detected for file %s", fname) - } - } - - // Read the included file - file, err := os.Open(path) - if err != nil { - return "", fmt.Errorf("error opening include file: %w", err) - } - defer file.Close() - - // Create buffer for output - var buf bytes.Buffer - - var tplData interface{} - switch len(data) { - case 0: - tplData = nil - case 1: - tplData = data[0] - default: - return "", fmt.Errorf("invalid number of arguments for includeFile: %d", len(data)) - } - - // Create new context with updated baseDir and include stack - includeCtx := &TemplateContext{ - baseDir: filepath.Dir(path), - Data: tplData, - Functions: ctx.Functions, - includeStack: append(append([]string{}, ctx.includeStack...), absPath), - } - - // Process the included template - if err := includeCtx.InstantiateTemplate(file, &buf); err != nil { - return "", fmt.Errorf("error processing include file: %w", err) - } - - // Parse the buffer content as YAML - var yamlData interface{} - if err := yaml.Unmarshal(buf.Bytes(), &yamlData); err != nil { - return "", fmt.Errorf("error parsing YAML: %w", err) - } - - // Convert to JSON - jsonBytes, err := json.Marshal(yamlData) - if err != nil { - return "", fmt.Errorf("error converting to JSON: %w", err) - } - - return string(jsonBytes), nil -} - -// InstantiateTemplate reads a template from the reader, executes it with the context, -// and writes the result to the writer -func (ctx *TemplateContext) InstantiateTemplate(reader io.Reader, writer io.Writer) error { - // Read template content - templateBytes, err := io.ReadAll(reader) - if err != nil { - return fmt.Errorf("failed to read template: %w", err) - } - - // Convert TemplateFunc map to FuncMap - funcMap := template.FuncMap{ - "include": ctx.includeFile, - } - for name, fn := range ctx.Functions { - funcMap[name] = fn - } - - // Create template with helper functions and option to error on missing fields - tmpl := template.New("template"). - Funcs(sprig.TxtFuncMap()). - Funcs(funcMap). - Option("missingkey=error") - - // Parse template - tmpl, err = tmpl.Parse(string(templateBytes)) - if err != nil { - return fmt.Errorf("failed to parse template: %w", err) - } - - // Execute template into a buffer - var buf bytes.Buffer - if err := tmpl.Execute(&buf, ctx.Data); err != nil { - return fmt.Errorf("failed to execute template: %w", err) - } - - // If this is the top-level rendering, we want to write the output as "pretty" YAML - if len(ctx.includeStack) == 0 { - var yamlData interface{} - // Parse the buffer content as YAML - if err := yaml.Unmarshal(buf.Bytes(), &yamlData); err != nil { - return fmt.Errorf("error parsing template output as YAML: %w. Template output: %s", err, buf.String()) - } - - // Create YAML encoder with default indentation - encoder := yaml.NewEncoder(writer) - encoder.SetIndent(2) - - // Write the YAML document - if err := encoder.Encode(yamlData); err != nil { - return fmt.Errorf("error writing YAML output: %w", err) - } - - } else { - // Otherwise, just write the buffer content to the writer - if _, err := buf.WriteTo(writer); err != nil { - return fmt.Errorf("failed to write template output: %w", err) - } - } - - return nil -} diff --git a/kurtosis-devnet/pkg/tmpl/tmpl_test.go b/kurtosis-devnet/pkg/tmpl/tmpl_test.go deleted file mode 100644 index 71c8a6985726d..0000000000000 --- a/kurtosis-devnet/pkg/tmpl/tmpl_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package tmpl - -import ( - "bytes" - "strings" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestNewTemplateContext(t *testing.T) { - t.Run("creates empty context", func(t *testing.T) { - ctx := NewTemplateContext() - require.Nil(t, ctx.Data, "expected nil Data in new context") - require.Empty(t, ctx.Functions, "expected empty Functions map in new context") - }) - - t.Run("adds data with WithData option", func(t *testing.T) { - data := map[string]string{"key": "value"} - ctx := NewTemplateContext(WithData(data)) - require.NotNil(t, ctx.Data, "expected non-nil Data in context") - d, ok := ctx.Data.(map[string]string) - require.True(t, ok) - require.Equal(t, "value", d["key"]) - }) - - t.Run("adds function with WithFunction option", func(t *testing.T) { - fn := func(s string) (string, error) { return s + "test", nil } - ctx := NewTemplateContext(WithFunction("testfn", fn)) - require.Len(t, ctx.Functions, 1, "expected one function in context") - _, ok := ctx.Functions["testfn"] - require.True(t, ok, "function not added with correct name") - }) -} - -func TestInstantiateTemplate(t *testing.T) { - t.Run("simple template substitution", func(t *testing.T) { - data := map[string]string{"name": "world"} - ctx := NewTemplateContext(WithData(data)) - - input := strings.NewReader("Hello {{.name}}!") - var output bytes.Buffer - - err := ctx.InstantiateTemplate(input, &output) - require.NoError(t, err) - - expected := "Hello world!\n" - require.Equal(t, expected, output.String()) - }) - - t.Run("template with custom function", func(t *testing.T) { - upper := func(s string) (string, error) { return strings.ToUpper(s), nil } - ctx := NewTemplateContext( - WithData(map[string]string{"name": "world"}), - WithFunction("upper", upper), - ) - - input := strings.NewReader("Hello {{upper .name}}!") - var output bytes.Buffer - - err := ctx.InstantiateTemplate(input, &output) - require.NoError(t, err) - - expected := "Hello WORLD!\n" - require.Equal(t, expected, output.String()) - }) - - t.Run("invalid template syntax", func(t *testing.T) { - ctx := NewTemplateContext() - input := strings.NewReader("Hello {{.name") - var output bytes.Buffer - - err := ctx.InstantiateTemplate(input, &output) - require.Error(t, err, "expected error for invalid template syntax") - }) - - t.Run("missing data field", func(t *testing.T) { - ctx := NewTemplateContext() - input := strings.NewReader("Hello {{.name}}!") - var output bytes.Buffer - - err := ctx.InstantiateTemplate(input, &output) - require.Error(t, err, "expected error for missing data field") - }) - - t.Run("multiple functions and data fields", func(t *testing.T) { - upper := func(s string) (string, error) { return strings.ToUpper(s), nil } - lower := func(s string) (string, error) { return strings.ToLower(s), nil } - - data := map[string]string{ - "greeting": "Hello", - "name": "World", - } - - ctx := NewTemplateContext( - WithData(data), - WithFunction("upper", upper), - WithFunction("lower", lower), - ) - - input := strings.NewReader("{{upper .greeting}} {{lower .name}}!") - var output bytes.Buffer - - err := ctx.InstantiateTemplate(input, &output) - require.NoError(t, err) - - expected := "HELLO world!\n" - require.Equal(t, expected, output.String()) - }) -} diff --git a/kurtosis-devnet/pkg/types/autofix.go b/kurtosis-devnet/pkg/types/autofix.go deleted file mode 100644 index 80c01b52df97a..0000000000000 --- a/kurtosis-devnet/pkg/types/autofix.go +++ /dev/null @@ -1,9 +0,0 @@ -package types - -type AutofixMode string - -const ( - AutofixModeDisabled AutofixMode = "disabled" - AutofixModeNormal AutofixMode = "normal" - AutofixModeNuke AutofixMode = "nuke" -) diff --git a/kurtosis-devnet/pkg/util/docker.go b/kurtosis-devnet/pkg/util/docker.go deleted file mode 100644 index b7a322a043814..0000000000000 --- a/kurtosis-devnet/pkg/util/docker.go +++ /dev/null @@ -1,773 +0,0 @@ -package util - -import ( - "context" - "fmt" - "net/http" - "os" - "path/filepath" - "strings" - "time" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/api/types/volume" - "github.com/docker/docker/client" - "github.com/docker/go-connections/nat" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" - - opClient "github.com/ethereum-optimism/optimism/op-service/client" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/sources" -) - -type TraefikHostTransport struct { - base http.RoundTripper - host string -} - -func (t *TraefikHostTransport) RoundTrip(req *http.Request) (*http.Response, error) { - newReq := req.Clone(req.Context()) - newReq.Host = t.host - newReq.Header.Set("Host", t.host) - return t.base.RoundTrip(newReq) -} - -type ServiceWithoutLabels struct { - Name string - ServiceUUID string - EnclaveUUID string - Ports []ServicePort -} - -type ServicePort struct { - Name string - Port int -} - -type RPCEndpoint struct { - Name string - Port int - UUID string - EnclaveUUID string -} - -// NewDockerClient creates a new Docker client and checks if Docker is available -func NewDockerClient() (*client.Client, error) { - apiClient, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - return nil, fmt.Errorf("failed to create docker client: %w", err) - } - - _, err = apiClient.Ping(context.Background()) - if err != nil { - return nil, fmt.Errorf("failed to connect to Docker: %w", err) - } - - return apiClient, nil -} - -// createKurtosisFilter creates a filter for kurtosis resources -func createKurtosisFilter(enclave ...string) filters.Args { - kurtosisFilter := filters.NewArgs() - if len(enclave) > 0 { - kurtosisFilter.Add("label", fmt.Sprintf("kurtosis.devnet.enclave=%s", enclave[0])) - } else { - kurtosisFilter.Add("label", "kurtosis.devnet.enclave") - } - return kurtosisFilter -} - -// destroyContainers stops and removes containers matching the filter -func destroyContainers(ctx context.Context, apiClient *client.Client, filter filters.Args) error { - containers, err := apiClient.ContainerList(ctx, container.ListOptions{ - All: true, - Filters: filter, - }) - if err != nil { - return fmt.Errorf("failed to list containers: %w", err) - } - - for _, cont := range containers { - if cont.State == "running" { - timeoutSecs := int(10) - if err := apiClient.ContainerStop(ctx, cont.ID, container.StopOptions{ - Timeout: &timeoutSecs, - }); err != nil { - return fmt.Errorf("failed to stop container %s: %w", cont.ID, err) - } - } - - if err := apiClient.ContainerRemove(ctx, cont.ID, container.RemoveOptions{ - RemoveVolumes: true, - Force: true, - }); err != nil { - return fmt.Errorf("failed to remove container %s: %w", cont.ID, err) - } - } - return nil -} - -// destroyVolumes removes volumes matching the filter -func destroyVolumes(ctx context.Context, apiClient *client.Client, filter filters.Args) error { - volumes, err := apiClient.VolumeList(ctx, volume.ListOptions{ - Filters: filter, - }) - if err != nil { - return fmt.Errorf("failed to list volumes: %w", err) - } - - for _, volume := range volumes.Volumes { - if err := apiClient.VolumeRemove(ctx, volume.Name, true); err != nil { - return fmt.Errorf("failed to remove volume %s: %w", volume.Name, err) - } - } - return nil -} - -// destroyNetworks removes networks matching the filter -func destroyNetworks(ctx context.Context, apiClient *client.Client, enclaveName string) error { - networks, err := apiClient.NetworkList(ctx, network.ListOptions{}) - if err != nil { - return fmt.Errorf("failed to list networks: %w", err) - } - - for _, network := range networks { - if (enclaveName != "" && strings.HasPrefix(network.Name, fmt.Sprintf("kt-%s-devnet", enclaveName))) || - (enclaveName == "" && strings.Contains(network.Name, "kt-")) { - if err := apiClient.NetworkRemove(ctx, network.ID); err != nil { - return fmt.Errorf("failed to remove network: %w", err) - } - } - } - return nil -} - -// DestroyDockerResources removes all Docker resources associated with the given enclave -func DestroyDockerResources(ctx context.Context, enclave ...string) error { - apiClient, err := NewDockerClient() - if err != nil { - return err - } - - enclaveName := "" - if len(enclave) > 0 { - enclaveName = enclave[0] - } - fmt.Printf("Destroying docker resources for enclave: %s\n", enclaveName) - - filter := createKurtosisFilter(enclave...) - - if err := destroyContainers(ctx, apiClient, filter); err != nil { - fmt.Printf("failed to destroy containers: %v", err) - } - - if err := destroyVolumes(ctx, apiClient, filter); err != nil { - fmt.Printf("failed to destroy volumes: %v", err) - } - - if err := destroyNetworks(ctx, apiClient, enclaveName); err != nil { - fmt.Printf("failed to destroy networks: %v", err) - } - - return nil -} - -func findRPCEndpoints(ctx context.Context, apiClient *client.Client) ([]RPCEndpoint, error) { - userFilters := filters.NewArgs() - userFilters.Add("label", "com.kurtosistech.container-type=user-service") - - containers, err := apiClient.ContainerList(ctx, container.ListOptions{ - All: false, - Filters: userFilters, - }) - if err != nil { - return nil, fmt.Errorf("failed to list containers: %w", err) - } - - var endpoints []RPCEndpoint - seen := make(map[string]bool) - - for _, c := range containers { - serviceName := strings.TrimPrefix(c.Names[0], "/") - serviceUUID := c.Labels["com.kurtosistech.guid"] - enclaveUUID := c.Labels["com.kurtosistech.enclave-id"] - - for _, port := range c.Ports { - if port.PrivatePort == 8545 && port.PublicPort != 0 { - key := fmt.Sprintf("%s-%s", serviceName, serviceUUID) - if !seen[key] { - seen[key] = true - endpoints = append(endpoints, RPCEndpoint{ - Name: serviceName, - Port: 8545, - UUID: serviceUUID, - EnclaveUUID: enclaveUUID, - }) - } - } - } - } - - return endpoints, nil -} - -func testRPCEndpoint(endpoint RPCEndpoint) error { - shortUUID := shortenedUUIDString(endpoint.UUID) - shortEnclaveUUID := shortenedUUIDString(endpoint.EnclaveUUID) - - hostHeader := fmt.Sprintf("%d-%s-%s", endpoint.Port, shortUUID, shortEnclaveUUID) - httpClient := &http.Client{ - Timeout: 10 * time.Second, - Transport: &TraefikHostTransport{ - base: http.DefaultTransport, - host: hostHeader, - }, - } - - rpcClient, err := rpc.DialOptions(context.Background(), "http://127.0.0.1:9730", rpc.WithHTTPClient(httpClient)) - if err != nil { - return fmt.Errorf("failed to create RPC client: %w", err) - } - defer rpcClient.Close() - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - if strings.Contains(endpoint.Name, "supervisor") { - return testSupervisor(ctx, rpcClient) - } - if strings.Contains(endpoint.Name, "test-sequencer") { - // TODO: No public or unauthenticated health/status API exists for test-sequencer yet. - // Admin API is still in progress — skip readiness check until it's available. - return nil - } - return testEthNode(ctx, rpcClient) -} - -func testEthNode(ctx context.Context, rpcClient *rpc.Client) error { - baseClient := opClient.NewBaseRPCClient(rpcClient) - - ethConfig := &sources.EthClientConfig{ - MaxRequestsPerBatch: 1, - MaxConcurrentRequests: 1, - ReceiptsCacheSize: 1, - TransactionsCacheSize: 1, - HeadersCacheSize: 1, - PayloadsCacheSize: 1, - BlockRefsCacheSize: 1, - TrustRPC: true, - MustBePostMerge: false, - RPCProviderKind: sources.RPCKindStandard, - MethodResetDuration: time.Minute, - } - - ethClient, err := sources.NewEthClient(baseClient, log.Root(), nil, ethConfig) - if err != nil { - return fmt.Errorf("failed to create EthClient: %w", err) - } - - blockRef, err := ethClient.BlockRefByLabel(ctx, eth.Unsafe) - if err != nil { - return fmt.Errorf("failed to get latest block: %w", err) - } - - if blockRef.Number == 0 && blockRef.Hash.String() == "0x0000000000000000000000000000000000000000000000000000000000000000" { - return fmt.Errorf("received invalid block reference") - } - - blockInfo, err := ethClient.InfoByNumber(ctx, blockRef.Number) - if err != nil { - return fmt.Errorf("failed to get block info by number: %w", err) - } - - if blockInfo.Hash() != blockRef.Hash { - return fmt.Errorf("block hash mismatch: expected %s, got %s", blockRef.Hash, blockInfo.Hash()) - } - - return nil -} - -func testSupervisor(ctx context.Context, rpcClient *rpc.Client) error { - var syncStatus interface{} - err := rpcClient.CallContext(ctx, &syncStatus, "supervisor_syncStatus") - if err != nil { - return fmt.Errorf("failed to call supervisor_syncStatus: %w", err) - } - - if syncStatus == nil { - return fmt.Errorf("supervisor_syncStatus returned nil") - } - - return nil -} - -// SetReverseProxyConfig recreates the Traefik container with correct configuration for service routing -func SetReverseProxyConfig(ctx context.Context) error { - apiClient, err := NewDockerClient() - if err != nil { - return fmt.Errorf("failed to create Docker client: %w", err) - } - - fmt.Printf("Fixing Traefik routing by recreating container\n") - - traefikFilters := filters.NewArgs() - traefikFilters.Add("name", "kurtosis-reverse-proxy") - - traefikContainers, err := apiClient.ContainerList(ctx, container.ListOptions{ - All: false, - Filters: traefikFilters, - }) - if err != nil { - return fmt.Errorf("failed to list containers: %w", err) - } - - var traefikContainer *types.Container - for _, c := range traefikContainers { - for _, name := range c.Names { - if strings.Contains(name, "kurtosis-reverse-proxy") { - traefikContainer = &c - break - } - } - if traefikContainer != nil { - break - } - } - - if traefikContainer == nil { - return fmt.Errorf("traefik container (kurtosis-reverse-proxy) not found, recreate it by restarting kurtosis (kurtosis engine restart)") - } - - fmt.Printf("Found Traefik container: %s\n", traefikContainer.ID[:12]) - - containerInfo, err := apiClient.ContainerInspect(ctx, traefikContainer.ID) - if err != nil { - return fmt.Errorf("failed to inspect container: %w", err) - } - containerName := strings.TrimPrefix(containerInfo.Name, "/") - containerImage := containerInfo.Config.Image - var portBindings []string - for containerPort, hostBindings := range containerInfo.HostConfig.PortBindings { - for _, binding := range hostBindings { - portBindings = append(portBindings, fmt.Sprintf("%s:%s", binding.HostPort, containerPort)) - } - } - var networks []string - for networkName := range containerInfo.NetworkSettings.Networks { - if networkName != "bridge" { - networks = append(networks, networkName) - } - } - var correctNetworkID string - for networkName, network := range containerInfo.NetworkSettings.Networks { - if networkName != "bridge" && strings.Contains(networkName, "kt-") { - correctNetworkID = network.NetworkID - break - } - } - - if correctNetworkID == "" { - return fmt.Errorf("traefik container is not connected to any kurtosis networks") - } - - tempDir, err := createTempConfigDir(ctx) - if err != nil { - return fmt.Errorf("failed to create temp config directory: %w", err) - } - defer func() { - if err := removeTempDir(tempDir); err != nil { - fmt.Printf("Warning: Failed to clean up temp directory %s: %v\n", tempDir, err) - } - }() - - fmt.Printf("Stopping current Traefik container\n") - timeout := int(10) - if err := apiClient.ContainerStop(ctx, traefikContainer.ID, container.StopOptions{ - Timeout: &timeout, - }); err != nil { - return fmt.Errorf("failed to stop container: %w", err) - } - - if err := apiClient.ContainerRemove(ctx, traefikContainer.ID, container.RemoveOptions{ - RemoveVolumes: true, - Force: true, - }); err != nil { - return fmt.Errorf("failed to remove container: %w", err) - } - - newContainer, err := recreateTraefikContainer(ctx, apiClient, containerName, containerImage, portBindings, tempDir, containerInfo.Config.Labels) - if err != nil { - return fmt.Errorf("failed to recreate container: %w", err) - } - - fmt.Printf("Created new Traefik container: %s\n", newContainer.ID[:12]) - for _, networkName := range networks { - if err := apiClient.NetworkConnect(ctx, networkName, newContainer.ID, nil); err != nil { - return fmt.Errorf("failed to connect to network %s: %w", networkName, err) - } - } - - if err := waitForContainerRunning(ctx, apiClient, newContainer.ID, 30*time.Second); err != nil { - return fmt.Errorf("container failed to start within timeout: %w", err) - } - - if err := waitForTraefikReady(ctx, 30*time.Second); err != nil { - fmt.Printf("Warning: Traefik API not ready within timeout: %v\n", err) - } - - if err := TestRPCEndpoints(ctx, apiClient); err != nil { - fmt.Printf("RPC access test failed: %v\n", err) - } - - fmt.Printf("Traefik routing fix completed successfully\n") - - return nil -} - -// waitForContainerRunning polls the container status until it's running or timeout -func waitForContainerRunning(ctx context.Context, apiClient *client.Client, containerID string, timeout time.Duration) error { - ctx, cancel := context.WithTimeout(ctx, timeout) - defer cancel() - - ticker := time.NewTicker(500 * time.Millisecond) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return fmt.Errorf("timeout waiting for container to start") - case <-ticker.C: - containerInfo, err := apiClient.ContainerInspect(ctx, containerID) - if err != nil { - return fmt.Errorf("failed to inspect container: %w", err) - } - if containerInfo.State.Running { - return nil - } - if containerInfo.State.Status == "exited" { - return fmt.Errorf("container exited unexpectedly: %s", containerInfo.State.Error) - } - } - } -} - -// waitForTraefikReady waits for Traefik API to be accessible -func waitForTraefikReady(ctx context.Context, timeout time.Duration) error { - ctx, cancel := context.WithTimeout(ctx, timeout) - defer cancel() - - ticker := time.NewTicker(500 * time.Millisecond) - defer ticker.Stop() - - client := &http.Client{ - Timeout: 2 * time.Second, - } - - for { - select { - case <-ctx.Done(): - return fmt.Errorf("timeout waiting for Traefik API to be ready") - case <-ticker.C: - resp, err := client.Get("http://127.0.0.1:9731/api/rawdata") - if err == nil && resp.StatusCode == http.StatusOK { - resp.Body.Close() - return nil - } - if resp != nil { - resp.Body.Close() - } - } - } -} - -func createTempConfigDir(ctx context.Context) (string, error) { - tempDir, err := os.MkdirTemp("", "traefik-fix-*") - if err != nil { - return "", fmt.Errorf("failed to create temp directory: %w", err) - } - - apiClient, err := NewDockerClient() - if err != nil { - os.RemoveAll(tempDir) - return "", fmt.Errorf("failed to create Docker client: %w", err) - } - defer apiClient.Close() - - networks, err := apiClient.NetworkList(ctx, network.ListOptions{}) - if err != nil { - os.RemoveAll(tempDir) - return "", fmt.Errorf("failed to list networks: %w", err) - } - - var traefikConfig strings.Builder - traefikConfig.WriteString(`# Traefik configuration with corrected network ID -api: - dashboard: true - insecure: true - -entryPoints: - web: - address: ":9730" - traefik: - address: ":9731" - -providers: -`) - - for _, net := range networks { - // Only include Kurtosis networks (skip bridge, host, none) - switch net.Name { - case "bridge", "host", "none": - continue - } - traefikConfig.WriteString(fmt.Sprintf(` docker: - endpoint: "unix:///var/run/docker.sock" - network: "%s" - exposedByDefault: true -`, net.ID)) - break - } - - traefikConfig.WriteString(` file: - directory: /etc/traefik/dynamic - watch: true - -log: - level: INFO -`) - configPath := filepath.Join(tempDir, "traefik.yml") - if err := os.WriteFile(configPath, []byte(traefikConfig.String()), 0644); err != nil { - os.RemoveAll(tempDir) - return "", fmt.Errorf("failed to write traefik config: %w", err) - } - - dynamicDir := filepath.Join(tempDir, "dynamic") - if err := os.MkdirAll(dynamicDir, 0755); err != nil { - os.RemoveAll(tempDir) - return "", fmt.Errorf("failed to create dynamic directory: %w", err) - } - - servicesWithoutLabels, err := discoverServicesWithoutTraefikLabels(ctx, apiClient) - if err != nil { - fmt.Printf("⚠️ Warning: Failed to discover services without Traefik labels: %v\n", err) - servicesWithoutLabels = []ServiceWithoutLabels{} - } - var dynamicConfig strings.Builder - dynamicConfig.WriteString("# Dynamic Traefik configuration for services without Traefik labels\n") - dynamicConfig.WriteString("# Generated by SetReverseProxyConfig - do not edit manually\n") - dynamicConfig.WriteString("http:\n") - dynamicConfig.WriteString(" routers:\n") - - for _, service := range servicesWithoutLabels { - addServiceRouters(&dynamicConfig, service) - } - dynamicConfig.WriteString(" services:\n") - for _, service := range servicesWithoutLabels { - addServiceServices(&dynamicConfig, service) - } - dynamicPath := filepath.Join(dynamicDir, "l1-routing.yml") - if err := os.WriteFile(dynamicPath, []byte(dynamicConfig.String()), 0644); err != nil { - os.RemoveAll(tempDir) - return "", fmt.Errorf("failed to write dynamic config: %w", err) - } - fmt.Printf("Created dynamic routing rules for %d services\n", len(servicesWithoutLabels)) - return tempDir, nil -} - -func removeTempDir(tempDir string) error { - return os.RemoveAll(tempDir) -} - -func recreateTraefikContainer(ctx context.Context, apiClient *client.Client, containerName, containerImage string, portBindings []string, tempDir string, labels map[string]string) (*container.CreateResponse, error) { - exposedPorts := make(nat.PortSet) - portBindingsMap := make(nat.PortMap) - - for _, binding := range portBindings { - parts := strings.Split(binding, ":") - if len(parts) == 2 { - hostPort := parts[0] - containerPortStr := parts[1] - - if strings.Contains(containerPortStr, "/") { - containerPortStr = strings.Split(containerPortStr, "/")[0] - } - - containerPort, err := nat.NewPort("tcp", containerPortStr) - if err != nil { - return nil, fmt.Errorf("invalid container port %s: %w", containerPortStr, err) - } - exposedPorts[containerPort] = struct{}{} - var portBindings []nat.PortBinding - portBindings = append(portBindings, nat.PortBinding{ - HostIP: "0.0.0.0", - HostPort: hostPort, - }) - portBindingsMap[containerPort] = portBindings - } - } - - mounts := []string{ - fmt.Sprintf("%s/traefik.yml:/etc/traefik/traefik.yml:ro", tempDir), - fmt.Sprintf("%s/dynamic:/etc/traefik/dynamic:ro", tempDir), - "/var/run/docker.sock:/var/run/docker.sock:ro", - } - - config := &container.Config{ - Image: containerImage, - ExposedPorts: exposedPorts, - Labels: labels, - } - hostConfig := &container.HostConfig{ - Binds: mounts, - PortBindings: portBindingsMap, - } - resp, err := apiClient.ContainerCreate(ctx, config, hostConfig, nil, nil, containerName) - if err != nil { - return nil, fmt.Errorf("failed to create container: %w", err) - } - if err := apiClient.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { - return nil, fmt.Errorf("failed to start container: %w", err) - } - - return &resp, nil -} - -func TestRPCEndpoints(ctx context.Context, apiClient *client.Client) error { - endpoints, err := findRPCEndpoints(ctx, apiClient) - if err != nil { - return fmt.Errorf("failed to find RPC endpoints: %w", err) - } - - if len(endpoints) == 0 { - fmt.Printf("No RPC endpoints found\n") - return nil - } - - fmt.Printf("Found %d RPC endpoint(s)\n", len(endpoints)) - - var lastError error - successCount := 0 - for _, endpoint := range endpoints { - fmt.Printf("Testing %s", endpoint.Name) - if err := testRPCEndpoint(endpoint); err != nil { - fmt.Printf(" - Failed: %v\n", err) - lastError = err - } else { - fmt.Printf(" - Success\n") - successCount++ - } - } - - if successCount == 0 { - return fmt.Errorf("all RPC endpoints failed, last error: %w", lastError) - } - - fmt.Printf("RPC access test passed (%d/%d endpoints working)\n", successCount, len(endpoints)) - return nil -} - -// shortenedUUIDString returns the first 12 characters of a UUID -func shortenedUUIDString(fullUUID string) string { - lengthToTrim := 12 - if lengthToTrim > len(fullUUID) { - lengthToTrim = len(fullUUID) - } - return fullUUID[:lengthToTrim] -} - -// discoverServicesWithoutTraefikLabels discovers services that need Traefik routing rules -func discoverServicesWithoutTraefikLabels(ctx context.Context, apiClient *client.Client) ([]ServiceWithoutLabels, error) { - userFilters := filters.NewArgs() - userFilters.Add("label", "com.kurtosistech.container-type=user-service") - - containers, err := apiClient.ContainerList(ctx, container.ListOptions{ - All: false, - Filters: userFilters, - }) - if err != nil { - return nil, fmt.Errorf("failed to list containers: %w", err) - } - - var servicesWithoutLabels []ServiceWithoutLabels - - for _, c := range containers { - serviceName := strings.TrimPrefix(c.Names[0], "/") - serviceUUID := c.Labels["com.kurtosistech.guid"] - enclaveUUID := c.Labels["com.kurtosistech.enclave-id"] - - containerDetails, err := apiClient.ContainerInspect(ctx, c.ID) - if err != nil { - fmt.Printf("failed to inspect container %s: %v\n", serviceName, err) - continue - } - - var portsWithoutLabels []ServicePort - processedPorts := make(map[int]bool) - - for portSpec := range containerDetails.Config.ExposedPorts { - port := portSpec.Port() - portNum := 0 - if _, err := fmt.Sscanf(port, "%d", &portNum); err == nil { - if processedPorts[portNum] { - continue - } - processedPorts[portNum] = true - hasTraefikLabelForPort := false - for labelKey := range c.Labels { - if strings.Contains(labelKey, "traefik.http.routers.") && strings.Contains(labelKey, ".rule") { - if strings.Contains(labelKey, fmt.Sprintf("-%d", portNum)) { - hasTraefikLabelForPort = true - break - } - } - } - if !hasTraefikLabelForPort { - portsWithoutLabels = append(portsWithoutLabels, ServicePort{ - Name: port, - Port: portNum, - }) - } - } - } - - if len(portsWithoutLabels) > 0 { - servicesWithoutLabels = append(servicesWithoutLabels, ServiceWithoutLabels{ - Name: serviceName, - ServiceUUID: serviceUUID, - EnclaveUUID: enclaveUUID, - Ports: portsWithoutLabels, - }) - } - } - - fmt.Printf("Discovered %d services with ports needing Traefik labels\n", len(servicesWithoutLabels)) - return servicesWithoutLabels, nil -} - -// addServiceRouters adds Traefik router rules for a service and its ports -func addServiceRouters(dynamicConfig *strings.Builder, service ServiceWithoutLabels) { - shortServiceUUID := shortenedUUIDString(service.ServiceUUID) - shortEnclaveUUID := shortenedUUIDString(service.EnclaveUUID) - for _, port := range service.Ports { - routerName := fmt.Sprintf("%s-%s", service.Name, port.Name) - serviceName := fmt.Sprintf("%s-%s", service.Name, port.Name) - dynamicConfig.WriteString(fmt.Sprintf(" %s:\n", routerName)) - dynamicConfig.WriteString(fmt.Sprintf(" rule: \"HostRegexp(`{^name:%d-%s-%s-?.*$}`)\"\n", port.Port, shortServiceUUID, shortEnclaveUUID)) - dynamicConfig.WriteString(fmt.Sprintf(" service: \"%s\"\n", serviceName)) - } -} - -// addServiceServices adds Traefik service definitions for a service and its ports -func addServiceServices(dynamicConfig *strings.Builder, service ServiceWithoutLabels) { - for _, port := range service.Ports { - serviceName := fmt.Sprintf("%s-%s", service.Name, port.Name) - dynamicConfig.WriteString(fmt.Sprintf(" %s:\n", serviceName)) - dynamicConfig.WriteString(" loadBalancer:\n") - dynamicConfig.WriteString(" servers:\n") - dynamicConfig.WriteString(fmt.Sprintf(" - url: \"http://%s:%d\"\n", service.Name, port.Port)) - } -} diff --git a/kurtosis-devnet/pkg/util/docker_test.go b/kurtosis-devnet/pkg/util/docker_test.go deleted file mode 100644 index ec6161e9a833f..0000000000000 --- a/kurtosis-devnet/pkg/util/docker_test.go +++ /dev/null @@ -1,234 +0,0 @@ -package util - -import ( - "fmt" - "strings" - "testing" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/network" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCreateKurtosisFilter(t *testing.T) { - tests := []struct { - name string - enclave []string - expectedFilter string - }{ - { - name: "no enclave specified", - enclave: []string{}, - expectedFilter: "kurtosis.devnet.enclave", - }, - { - name: "enclave specified", - enclave: []string{"test-enclave"}, - expectedFilter: "kurtosis.devnet.enclave=test-enclave", - }, - { - name: "multiple enclaves (only first used)", - enclave: []string{"enclave1", "enclave2"}, - expectedFilter: "kurtosis.devnet.enclave=enclave1", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - filter := createKurtosisFilter(tt.enclave...) - - // Check that the filter has the expected label - labels := filter.Get("label") - require.Len(t, labels, 1, "Expected exactly one label filter") - assert.Equal(t, tt.expectedFilter, labels[0]) - }) - } -} - -// Helper function to create test containers for scenarios -func createTestContainer(id, name string, networks map[string]*network.EndpointSettings) types.Container { - return types.Container{ - ID: id, - Names: []string{name}, - NetworkSettings: &types.SummaryNetworkSettings{ - Networks: networks, - }, - } -} - -// Helper function to create test network endpoint -func createTestNetworkEndpoint(networkID string) *network.EndpointSettings { - return &network.EndpointSettings{ - NetworkID: networkID, - } -} - -func TestSetReverseProxyConfigLogic(t *testing.T) { - // Test the logic patterns that the function should follow - // The function should ALWAYS configure Traefik for ALL networks it has access to - - t.Run("network ID extraction logic", func(t *testing.T) { - // Test the logic for extracting ALL network IDs from Traefik's own networks - networks := map[string]*network.EndpointSettings{ - "bridge": createTestNetworkEndpoint("bridge-network-id"), - "custom1": createTestNetworkEndpoint("custom-network-id-1"), - "custom2": createTestNetworkEndpoint("custom-network-id-2"), - } - - traefikContainer := createTestContainer("traefik-id", "kurtosis-reverse-proxy-test", networks) - - // The function should collect all non-bridge networks - networkIDs := make(map[string]bool) - for networkName, network := range traefikContainer.NetworkSettings.Networks { - if networkName != "bridge" { - networkIDs[network.NetworkID] = true - } - } - - assert.Len(t, networkIDs, 2) - assert.Contains(t, networkIDs, "custom-network-id-1") - assert.Contains(t, networkIDs, "custom-network-id-2") - }) - - t.Run("traefik container identification", func(t *testing.T) { - // Test the logic for identifying Traefik containers - containers := []types.Container{ - createTestContainer("container1", "/some-other-container", nil), - createTestContainer("container2", "/kurtosis-reverse-proxy-12345", nil), - createTestContainer("container3", "/another-container", nil), - } - - // The function should find the container with "kurtosis-reverse-proxy" in the name - var traefikContainer *types.Container - for _, c := range containers { - if strings.Contains(c.Names[0], "kurtosis-reverse-proxy") { - traefikContainer = &c - break - } - } - - require.NotNil(t, traefikContainer, "Should find Traefik container") - assert.Equal(t, "container2", traefikContainer.ID) - }) - - t.Run("dynamic config generation", func(t *testing.T) { - // Test the dynamic configuration template for multiple networks - networkIDs := []string{"test-network-id-1", "test-network-id-2"} - - expectedConfig := `# Dynamic Traefik configuration for correct networks -providers: - dockerDynamic0: - endpoint: "unix:///var/run/docker.sock" - exposedByDefault: false - network: "test-network-id-1" - watch: true - dockerDynamic1: - endpoint: "unix:///var/run/docker.sock" - exposedByDefault: false - network: "test-network-id-2" - watch: true -` - - var actualConfig strings.Builder - actualConfig.WriteString("# Dynamic Traefik configuration for correct networks\n") - actualConfig.WriteString("providers:\n") - - for i, networkID := range networkIDs { - actualConfig.WriteString(fmt.Sprintf(` dockerDynamic%d: - endpoint: "unix:///var/run/docker.sock" - exposedByDefault: false - network: "%s" - watch: true -`, i, networkID)) - } - - assert.Equal(t, expectedConfig, actualConfig.String()) - }) -} - -func TestCheckUserServiceNetworks(t *testing.T) { - // Test the network accessibility checking logic - - t.Run("network accessibility logic", func(t *testing.T) { - // Test the logic for checking if user services have networks that Traefik doesn't have access to - - // Traefik has access to these networks - traefikNetworkIDs := map[string]bool{ - "network-1": true, - "network-2": true, - } - - // User service containers and their networks - userServiceContainers := []struct { - name string - networks map[string]*network.EndpointSettings - }{ - { - name: "service-1", - networks: map[string]*network.EndpointSettings{ - "bridge": createTestNetworkEndpoint("bridge-network-id"), - "network-1": createTestNetworkEndpoint("network-1"), // accessible - }, - }, - { - name: "service-2", - networks: map[string]*network.EndpointSettings{ - "bridge": createTestNetworkEndpoint("bridge-network-id"), - "network-2": createTestNetworkEndpoint("network-2"), // accessible - "network-3": createTestNetworkEndpoint("network-3"), // NOT accessible - }, - }, - } - - // Test the logic for finding unreachable networks - userServiceNetworks := make(map[string]bool) - for _, container := range userServiceContainers { - for networkName, network := range container.networks { - if networkName != "bridge" { - userServiceNetworks[network.NetworkID] = true - } - } - } - - // Find networks that user services are on but Traefik is not - unreachableNetworks := make(map[string]bool) - for networkID := range userServiceNetworks { - if !traefikNetworkIDs[networkID] { - unreachableNetworks[networkID] = true - } - } - - // Should find network-3 as unreachable - assert.Len(t, unreachableNetworks, 1) - assert.Contains(t, unreachableNetworks, "network-3") - assert.NotContains(t, unreachableNetworks, "network-1") - assert.NotContains(t, unreachableNetworks, "network-2") - }) - - t.Run("all networks accessible", func(t *testing.T) { - // Test case where all user service networks are accessible by Traefik - - traefikNetworkIDs := map[string]bool{ - "network-1": true, - "network-2": true, - "network-3": true, - } - - userServiceNetworks := map[string]bool{ - "network-1": true, - "network-2": true, - } - - // Find unreachable networks - unreachableNetworks := make(map[string]bool) - for networkID := range userServiceNetworks { - if !traefikNetworkIDs[networkID] { - unreachableNetworks[networkID] = true - } - } - - // Should find no unreachable networks - assert.Len(t, unreachableNetworks, 0) - }) -} diff --git a/kurtosis-devnet/pkg/util/retry.go b/kurtosis-devnet/pkg/util/retry.go deleted file mode 100644 index 05b7a9c38865f..0000000000000 --- a/kurtosis-devnet/pkg/util/retry.go +++ /dev/null @@ -1,35 +0,0 @@ -package util - -import ( - "context" - "fmt" - "log" - "time" -) - -// WithRetry executes a function with exponential backoff retry logic -// This is specifically designed for handling gRPC connection timeouts with Kurtosis -func WithRetry[T any](ctx context.Context, operation string, fn func() (T, error)) (T, error) { - var result T - var err error - - for attempt := 1; attempt <= 3; attempt++ { - result, err = fn() - if err == nil { - if attempt > 1 { - log.Printf("✅ Successfully completed %s on attempt %d", operation, attempt) - } - return result, nil - } - - log.Printf("❌ Attempt %d failed for %s: %v", attempt, operation, err) - - if attempt < 3 { - sleepDuration := time.Duration(attempt*2) * time.Second - log.Printf("⏳ Retrying %s in %v...", operation, sleepDuration) - time.Sleep(sleepDuration) - } - } - - return result, fmt.Errorf("%s failed after 3 attempts: %w", operation, err) -} diff --git a/kurtosis-devnet/pkg/util/retry_test.go b/kurtosis-devnet/pkg/util/retry_test.go deleted file mode 100644 index 7e4685dc7a532..0000000000000 --- a/kurtosis-devnet/pkg/util/retry_test.go +++ /dev/null @@ -1,255 +0,0 @@ -package util - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestWithRetry(t *testing.T) { - tests := []struct { - name string - operation string - setupFunc func() func() (string, error) - expectedResult string - expectError bool - expectedErrorString string - expectedAttempts int - timeout time.Duration - }{ - { - name: "success on first attempt", - operation: "test-operation", - setupFunc: func() func() (string, error) { - return func() (string, error) { - return "success", nil - } - }, - expectedResult: "success", - expectError: false, - expectedAttempts: 1, - timeout: 5 * time.Second, - }, - { - name: "success on second attempt", - operation: "test-operation", - setupFunc: func() func() (string, error) { - attempts := 0 - return func() (string, error) { - attempts++ - if attempts < 2 { - return "", errors.New("temporary failure") - } - return "success", nil - } - }, - expectedResult: "success", - expectError: false, - expectedAttempts: 2, - timeout: 10 * time.Second, - }, - { - name: "success on third attempt", - operation: "test-operation", - setupFunc: func() func() (string, error) { - attempts := 0 - return func() (string, error) { - attempts++ - if attempts < 3 { - return "", errors.New("temporary failure") - } - return "success", nil - } - }, - expectedResult: "success", - expectError: false, - expectedAttempts: 3, - timeout: 15 * time.Second, - }, - { - name: "failure after all attempts", - operation: "test-operation", - setupFunc: func() func() (string, error) { - return func() (string, error) { - return "", errors.New("persistent failure") - } - }, - expectedResult: "", - expectError: true, - expectedErrorString: "test-operation failed after 3 attempts: persistent failure", - expectedAttempts: 3, - timeout: 15 * time.Second, - }, - { - name: "different return type - int", - operation: "integer-operation", - setupFunc: func() func() (string, error) { - // Note: This test uses string but we'll test int in a separate function - return func() (string, error) { - return "42", nil - } - }, - expectedResult: "42", - expectError: false, - expectedAttempts: 1, - timeout: 5 * time.Second, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), tt.timeout) - defer cancel() - - fn := tt.setupFunc() - start := time.Now() - - result, err := WithRetry(ctx, tt.operation, fn) - - duration := time.Since(start) - - if tt.expectError { - assert.Error(t, err) - assert.Contains(t, err.Error(), tt.expectedErrorString) - assert.Equal(t, tt.expectedResult, result) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.expectedResult, result) - } - - // Verify timing for multi-attempt scenarios - if tt.expectedAttempts > 1 { - // Expected minimum time: (attempt-1) * 2 + (attempt-2) * 4 seconds - // For 2 attempts: 2 seconds minimum - // For 3 attempts: 2 + 4 = 6 seconds minimum - expectedMinDuration := time.Duration(0) - for i := 1; i < tt.expectedAttempts; i++ { - expectedMinDuration += time.Duration(i*2) * time.Second - } - - if tt.expectError && tt.expectedAttempts == 3 { - // Should take at least 6 seconds for 3 failed attempts - assert.True(t, duration >= expectedMinDuration, - "Expected at least %v but took %v", expectedMinDuration, duration) - } else if !tt.expectError && tt.expectedAttempts > 1 { - // Should take at least the retry delay time - assert.True(t, duration >= expectedMinDuration, - "Expected at least %v but took %v", expectedMinDuration, duration) - } - } - }) - } -} - -func TestWithRetryContextCancellation(t *testing.T) { - // Note: The current implementation doesn't actually respect context cancellation - // during sleep operations, so this test verifies the current behavior - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - - callCount := 0 - fn := func() (string, error) { - callCount++ - // Check if context is cancelled at the start of each call - select { - case <-ctx.Done(): - return "", ctx.Err() - default: - } - - // Always fail to test retry behavior - return "", errors.New("test failure") - } - - start := time.Now() - result, err := WithRetry(ctx, "context-cancel-test", fn) - duration := time.Since(start) - - assert.Error(t, err) - assert.Contains(t, err.Error(), "context-cancel-test failed after 3 attempts") - assert.Equal(t, "", result) - // The function should complete all 3 attempts even with context timeout - // because the current implementation doesn't respect context cancellation during sleep - assert.True(t, duration >= 6*time.Second, "Should complete all retry attempts") -} - -func TestWithRetryDifferentTypes(t *testing.T) { - t.Run("integer return type", func(t *testing.T) { - ctx := context.Background() - - fn := func() (int, error) { - return 42, nil - } - - result, err := WithRetry(ctx, "integer-test", fn) - - assert.NoError(t, err) - assert.Equal(t, 42, result) - }) - - t.Run("struct return type", func(t *testing.T) { - ctx := context.Background() - - type TestStruct struct { - Name string - Value int - } - - expected := TestStruct{Name: "test", Value: 123} - fn := func() (TestStruct, error) { - return expected, nil - } - - result, err := WithRetry(ctx, "struct-test", fn) - - assert.NoError(t, err) - assert.Equal(t, expected, result) - }) - - t.Run("pointer return type", func(t *testing.T) { - ctx := context.Background() - - expected := &struct{ Value string }{Value: "test"} - fn := func() (*struct{ Value string }, error) { - return expected, nil - } - - result, err := WithRetry(ctx, "pointer-test", fn) - - assert.NoError(t, err) - assert.Equal(t, expected, result) - }) -} - -func TestWithRetryErrorPropagation(t *testing.T) { - ctx := context.Background() - - originalErr := errors.New("original error") - fn := func() (string, error) { - return "", originalErr - } - - result, err := WithRetry(ctx, "error-propagation", fn) - - assert.Error(t, err) - assert.Equal(t, "", result) - assert.Contains(t, err.Error(), "error-propagation failed after 3 attempts") - assert.True(t, errors.Is(err, originalErr), "Should wrap the original error") -} - -func TestWithRetryOperationName(t *testing.T) { - ctx := context.Background() - - operationName := "custom-operation-name" - fn := func() (string, error) { - return "", errors.New("test error") - } - - _, err := WithRetry(ctx, operationName, fn) - - assert.Error(t, err) - assert.Contains(t, err.Error(), operationName) -} diff --git a/kurtosis-devnet/pkg/util/util.go b/kurtosis-devnet/pkg/util/util.go deleted file mode 100644 index ab871c2840007..0000000000000 --- a/kurtosis-devnet/pkg/util/util.go +++ /dev/null @@ -1,70 +0,0 @@ -package util - -import ( - "fmt" - "io" - "os" - "path/filepath" - - "github.com/spf13/afero" -) - -// CopyDir copies a directory from src to dst using the provided filesystem. -// If no filesystem is provided, it uses the OS filesystem. -func CopyDir(src string, dst string, fs afero.Fs) error { - if fs == nil { - fs = afero.NewOsFs() - } - - // First ensure the source exists - srcInfo, err := fs.Stat(src) - if err != nil { - return err - } - if !srcInfo.IsDir() { - return fmt.Errorf("source path %s is not a directory", src) - } - - // Create the destination directory - err = fs.MkdirAll(dst, srcInfo.Mode()) - if err != nil { - return err - } - - // Walk through the source directory - return afero.Walk(fs, src, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - // Get relative path - relPath, err := filepath.Rel(src, path) - if err != nil { - return err - } - - // Construct destination path - dstPath := filepath.Join(dst, relPath) - - if info.IsDir() { - // Create directories with same permissions - return fs.MkdirAll(dstPath, info.Mode()) - } - - // Copy files - srcFile, err := fs.Open(path) - if err != nil { - return err - } - defer srcFile.Close() - - dstFile, err := fs.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode()) - if err != nil { - return err - } - defer dstFile.Close() - - _, err = io.Copy(dstFile, srcFile) - return err - }) -} diff --git a/kurtosis-devnet/pkg/util/util_test.go b/kurtosis-devnet/pkg/util/util_test.go deleted file mode 100644 index 1b875b971555c..0000000000000 --- a/kurtosis-devnet/pkg/util/util_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package util - -import ( - "path/filepath" - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCopyDir(t *testing.T) { - tests := []struct { - name string - setupFiles map[string]string - src string - dst string - expectError bool - }{ - { - name: "successful copy of directory with files", - setupFiles: map[string]string{ - "/src/file1.txt": "content1", - "/src/file2.txt": "content2", - "/src/subdir/file3.txt": "content3", - }, - src: "/src", - dst: "/dst", - expectError: false, - }, - { - name: "source directory does not exist", - setupFiles: map[string]string{}, - src: "/nonexistent", - dst: "/dst", - expectError: true, - }, - { - name: "source is not a directory", - setupFiles: map[string]string{ - "/src": "file content", - }, - src: "/src", - dst: "/dst", - expectError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create a new memory filesystem for each test - fs := afero.NewMemMapFs() - - // Set up test files - for path, content := range tt.setupFiles { - dir := filepath.Dir(path) - err := fs.MkdirAll(dir, 0755) - require.NoError(t, err, "Failed to create directory") - - err = afero.WriteFile(fs, path, []byte(content), 0644) - require.NoError(t, err, "Failed to write test file") - } - - // Execute the copy - err := CopyDir(tt.src, tt.dst, fs) - - if tt.expectError { - assert.Error(t, err) - return - } - - assert.NoError(t, err) - - // Verify the copied files - for srcPath, expectedContent := range tt.setupFiles { - // Skip if the source path is not a file (e.g., when testing error cases) - info, err := fs.Stat(srcPath) - if err != nil || !info.Mode().IsRegular() { - continue - } - - // Calculate destination path - relPath, err := filepath.Rel(tt.src, srcPath) - require.NoError(t, err) - dstPath := filepath.Join(tt.dst, relPath) - - // Verify file exists and content matches - content, err := afero.ReadFile(fs, dstPath) - assert.NoError(t, err, "Failed to read copied file: %s", dstPath) - assert.Equal(t, expectedContent, string(content), "Content mismatch for file: %s", dstPath) - - // Verify permissions - srcInfo, err := fs.Stat(srcPath) - require.NoError(t, err) - dstInfo, err := fs.Stat(dstPath) - require.NoError(t, err) - assert.Equal(t, srcInfo.Mode(), dstInfo.Mode(), "Mode mismatch for file: %s", dstPath) - } - }) - } -} diff --git a/kurtosis-devnet/simple.yaml b/kurtosis-devnet/simple.yaml deleted file mode 100644 index 351ac0230ac5c..0000000000000 --- a/kurtosis-devnet/simple.yaml +++ /dev/null @@ -1,89 +0,0 @@ -optimism_package: - faucet: - enabled: true - image: {{ localDockerImage "op-faucet" }} - chains: - op-kurtosis: - participants: - node0: - el: - type: op-geth - image: "" - log_level: "" - extra_env_vars: {} - extra_labels: {} - extra_params: [] - tolerations: [] - volume_size: 0 - min_cpu: 0 - max_cpu: 0 - min_mem: 0 - max_mem: 0 - cl: - type: op-node - image: {{ localDockerImage "op-node" }} - log_level: "" - extra_env_vars: {} - extra_labels: {} - extra_params: [] - tolerations: [] - volume_size: 0 - min_cpu: 0 - max_cpu: 0 - min_mem: 0 - max_mem: 0 - mev_params: - image: "" - builder_host: "" - builder_port: "" - network_params: - network: "kurtosis" - network_id: "2151908" - seconds_per_slot: 2 - fjord_time_offset: 0 - granite_time_offset: 0 - holocene_time_offset: 0 - isthmus_time_offset: 0 - fund_dev_accounts: true - batcher_params: - image: {{ localDockerImage "op-batcher" }} - extra_params: [] - proposer_params: - image: {{ localDockerImage "op-proposer" }} - extra_params: [] - game_type: 1 - proposal_interval: 10m - challengers: - challenger: - enabled: true - image: {{ localDockerImage "op-challenger" }} - participants: "*" - cannon_prestates_url: {{ localPrestate.URL }} - cannon_trace_types: ["cannon", "permissioned"] - op_contract_deployer_params: - image: {{ localDockerImage "op-deployer" }} - l1_artifacts_locator: {{ localContractArtifacts "l1" }} - l2_artifacts_locator: {{ localContractArtifacts "l2" }} - overrides: - faultGameAbsolutePrestate: {{ localPrestate.Hashes.prestate_mt64 }} - global_log_level: "info" - global_node_selectors: {} - global_tolerations: [] - persistent: false -ethereum_package: - participants: - - el_type: geth - cl_type: teku - cl_image: consensys/teku:25.7.1 - network_params: - preset: minimal - genesis_delay: 5 - additional_preloaded_contracts: | - { - "0x4e59b44847b379578588920cA78FbF26c0B4956C": { - "balance": "0ETH", - "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", - "storage": {}, - "nonce": "1" - } - } diff --git a/kurtosis-devnet/templates/devnet.yaml b/kurtosis-devnet/templates/devnet.yaml deleted file mode 100644 index 032b2ef98f924..0000000000000 --- a/kurtosis-devnet/templates/devnet.yaml +++ /dev/null @@ -1,51 +0,0 @@ -{{- $context := or . (dict)}} -{{- $default_l2s := dict - "2151908" (dict "nodes" (list "op-geth")) - "2151909" (dict "nodes" (list "op-geth")) -}} -{{- $l2s := dig "l2s" $default_l2s $context }} -{{- $overrides := dig "overrides" (dict) $context }} -{{- $interop := dig "interop" false $context }} ---- -optimism_package: -{{ if $interop }} - interop: - enabled: true - supervisor_params: - image: {{ dig "overrides" "images" "op_supervisor" (localDockerImage "op-supervisor") $context }} - extra_params: - - {{ dig "overrides" "flags" "log_level" "!!str" $context }} -{{ end }} - chains: - {{ range $l2_id, $l2 := $l2s }} - op-kurtosis-{{ $l2_id }}: - {{ include "l2.yaml" (dict "chain_id" $l2_id "overrides" $overrides "nodes" $l2.nodes) }} - {{ end }} - op_contract_deployer_params: - image: {{ dig "overrides" "images" "op_deployer" (localDockerImage "op-deployer") $context }} - l1_artifacts_locator: {{ dig "overrides" "urls" "l1_artifacts" (localContractArtifacts "l1") $context }} - l2_artifacts_locator: {{ dig "overrides" "urls" "l2_artifacts" (localContractArtifacts "l2") $context }} -{{ if $interop }} - global_deploy_overrides: - faultGameAbsolutePrestate: {{ dig "overrides" "deployer" "prestate" (localPrestate.Hashes.prestate_mt64) $context }} -{{ end }} - global_log_level: "info" - global_node_selectors: {} - global_tolerations: [] - persistent: false -ethereum_package: - participants: - - el_type: geth - cl_type: teku - network_params: - preset: minimal - genesis_delay: 5 - additional_preloaded_contracts: | - { - "0x4e59b44847b379578588920cA78FbF26c0B4956C": { - "balance": "0ETH", - "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", - "storage": {}, - "nonce": "1" - } - } diff --git a/kurtosis-devnet/templates/l2.yaml b/kurtosis-devnet/templates/l2.yaml deleted file mode 100644 index 268cfb8a75fb5..0000000000000 --- a/kurtosis-devnet/templates/l2.yaml +++ /dev/null @@ -1,35 +0,0 @@ -{{- $context := or . (dict)}} -{{- $nodes := dig "nodes" (list "op-geth") $context -}} ---- -participants: -{{- range $node_id, $node := $nodes }} - {{ $node_id }}: - {{ include "local-op-node.yaml" (dict "overrides" $context.overrides "el_type" $node) }} -{{- end }} -network_params: - network: "kurtosis" - network_id: "{{ .chain_id }}" - seconds_per_slot: 2 - fjord_time_offset: 0 - granite_time_offset: 0 - holocene_time_offset: 0 - isthmus_time_offset: 0 - jovian_time_offset: 0 - interop_time_offset: 0 - fund_dev_accounts: true -batcher_params: - image: {{ dig "overrides" "images" "op_batcher" (localDockerImage "op-batcher") $context }} - extra_params: - - {{ dig "overrides" "flags" "log_level" "!!str" $context }} -challenger_params: - image: {{ dig "overrides" "images" "op_challenger" (localDockerImage "op-challenger") $context }} - cannon_prestate_path: "" - cannon_prestates_url: {{ dig "overrides" "urls" "prestate" (localPrestate.URL) $context }} - extra_params: - - {{ dig "overrides" "flags" "log_level" "!!str" $context }} -proposer_params: - image: {{ dig "overrides" "images" "op_proposer" (localDockerImage "op-proposer") $context }} - extra_params: - - {{ dig "overrides" "flags" "log_level" "!!str" $context }} - game_type: 1 - proposal_interval: 10m diff --git a/kurtosis-devnet/templates/local-op-node.yaml b/kurtosis-devnet/templates/local-op-node.yaml deleted file mode 100644 index 3b810bc9825ed..0000000000000 --- a/kurtosis-devnet/templates/local-op-node.yaml +++ /dev/null @@ -1,33 +0,0 @@ -{{- $context := or . (dict)}} -{{- $el_type := dig "el_type" "op-geth" $context -}} ---- -el: - type: {{ $el_type }} - image: {{ dig "overrides" "images" $el_type "!!str" $context }} - log_level: "" - extra_env_vars: {} - extra_labels: {} - extra_params: [] - tolerations: [] - volume_size: 0 - min_cpu: 0 - max_cpu: 0 - min_mem: 0 - max_mem: 0 -cl: - type: op-node - image: {{ dig "overrides" "images" "op-node" (localDockerImage "op-node") $context }} - log_level: "" - extra_env_vars: {} - extra_labels: {} - extra_params: [] - tolerations: [] - volume_size: 0 - min_cpu: 0 - max_cpu: 0 - min_mem: 0 - max_mem: 0 -mev_params: - image: "" - builder_host: "" - builder_port: "" \ No newline at end of file diff --git a/kurtosis-devnet/tests/.gitignore b/kurtosis-devnet/tests/.gitignore deleted file mode 100644 index e3339ba9266cb..0000000000000 --- a/kurtosis-devnet/tests/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.json -*.toml diff --git a/kurtosis-devnet/tests/boilerplate.sh b/kurtosis-devnet/tests/boilerplate.sh deleted file mode 100644 index 70e088ab7f3d5..0000000000000 --- a/kurtosis-devnet/tests/boilerplate.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Default values -DEVNET="" -ENVIRONMENT="" - -# Parse command line arguments -while [[ $# -gt 0 ]]; do - case "$1" in - --devnet) - DEVNET="$2" - shift 2 - ;; - --environment) - ENVIRONMENT="$2" - shift 2 - ;; - *) - echo "Invalid option: $1" >&2 - exit 1 - ;; - esac -done - -# Validate required arguments -if [ -z "$DEVNET" ]; then - echo "Error: --devnet argument is required" >&2 - exit 1 -fi - -if [ -z "$ENVIRONMENT" ]; then - echo "Error: --environment argument is required" >&2 - exit 1 -fi diff --git a/kurtosis-devnet/tests/interop-smoke-test.sh b/kurtosis-devnet/tests/interop-smoke-test.sh deleted file mode 100644 index 4fbc6ce0f3dd3..0000000000000 --- a/kurtosis-devnet/tests/interop-smoke-test.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -# TODO: actually test something. Right now it just gives an idea of what's -# possible. - -# shellcheck disable=SC1091 -source "$(dirname "$0")/boilerplate.sh" - -echo "DEVNET: $DEVNET" -echo "ENVIRONMENT:" -cat "$ENVIRONMENT" - -l1_name=$(cat "$ENVIRONMENT" | jq -r '.l1.name') -echo "L1 NAME: $l1_name" - -cast --version diff --git a/kurtosis-devnet/tests/kurtosis.yml b/kurtosis-devnet/tests/kurtosis.yml deleted file mode 100644 index 0fa3a586c742c..0000000000000 --- a/kurtosis-devnet/tests/kurtosis.yml +++ /dev/null @@ -1,4 +0,0 @@ -name: github.com/ethereum-optimism/optimism/kurtosis-devnet/tests -description: |- - Kurtosis package for running tests within the enclave -replace: {} diff --git a/kurtosis-devnet/tests/main.star b/kurtosis-devnet/tests/main.star deleted file mode 100644 index a962584f3c690..0000000000000 --- a/kurtosis-devnet/tests/main.star +++ /dev/null @@ -1,23 +0,0 @@ -""" -This is the main script for the kurtosis test runner. -""" - -def run(plan, devnet, timestamp, tests): - - tests_artifact = plan.upload_files( - src = "./", - name = "tests", - description = "uploading tests" - ) - - for test in tests: - plan.run_sh( - run = "/bin/bash /tests/{} --devnet {} --environment /tests/{}.json".format(test, devnet, devnet), - name = "{}-{}".format(test, timestamp), - image = "mslipper/deployment-utils:latest", - wait="180s", - - files = { - "/tests": tests_artifact, - }, - ) diff --git a/kurtosis-devnet/user.yaml b/kurtosis-devnet/user.yaml deleted file mode 100644 index 89b4fd84285a3..0000000000000 --- a/kurtosis-devnet/user.yaml +++ /dev/null @@ -1,3 +0,0 @@ -{{- $context := or . (dict)}} ---- -{{ include "templates/devnet.yaml" $context }} diff --git a/op-acceptance-tests/justfile b/op-acceptance-tests/justfile index 5547fb3c13d3a..27bdb5f8a1676 100644 --- a/op-acceptance-tests/justfile +++ b/op-acceptance-tests/justfile @@ -1,5 +1,4 @@ REPO_ROOT := `realpath ..` # path to the root of the optimism monorepo -KURTOSIS_DIR := REPO_ROOT + "/kurtosis-devnet" ACCEPTOR_VERSION := env_var_or_default("ACCEPTOR_VERSION", "v3.10.2") DOCKER_REGISTRY := env_var_or_default("DOCKER_REGISTRY", "us-docker.pkg.dev/oplabs-tools-artifacts/images") ACCEPTOR_IMAGE := env_var_or_default("ACCEPTOR_IMAGE", DOCKER_REGISTRY + "/op-acceptor:" + ACCEPTOR_VERSION) diff --git a/devnet-sdk/scripts/metrics-collect-authorship.sh b/op-acceptance-tests/scripts/metrics-collect-authorship.sh similarity index 84% rename from devnet-sdk/scripts/metrics-collect-authorship.sh rename to op-acceptance-tests/scripts/metrics-collect-authorship.sh index b498c7b566e61..cff633c087e01 100755 --- a/devnet-sdk/scripts/metrics-collect-authorship.sh +++ b/op-acceptance-tests/scripts/metrics-collect-authorship.sh @@ -8,7 +8,6 @@ if [ -z "$DIRECTORY" ]; then exit 1 fi -# Extract authorship data of target directory from git history echo -n "dt,author,commit" cd "$DIRECTORY" git ls-files | while read -r file; do diff --git a/op-acceptance-tests/tests/interop/contract/interop_contract_test.go b/op-acceptance-tests/tests/interop/contract/interop_contract_test.go index ae93f8ea2b118..ca5b6193b0b43 100644 --- a/op-acceptance-tests/tests/interop/contract/interop_contract_test.go +++ b/op-acceptance-tests/tests/interop/contract/interop_contract_test.go @@ -4,7 +4,7 @@ import ( "math/rand" "testing" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" + "github.com/ethereum-optimism/optimism/op-core/predeploys" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/presets" "github.com/ethereum-optimism/optimism/op-service/eth" @@ -43,7 +43,7 @@ func TestRegularMessage(gt *testing.T) { logger.Info("Send message", "address", eventLoggerAddress, "topicCnt", len(topics), "dataLen", len(data)) trigger := &txintent.SendTrigger{ - Emitter: constants.L2ToL2CrossDomainMessenger, + Emitter: predeploys.L2toL2CrossDomainMessengerAddr, DestChainID: bob.ChainID(), Target: eventLoggerAddress, RelayedCalldata: calldata, @@ -55,7 +55,7 @@ func TestRegularMessage(gt *testing.T) { sendMsgReceipt, err := txA.PlannedTx.Included.Eval(t.Ctx()) require.NoError(err, "send msg receipt not found") require.Equal(1, len(sendMsgReceipt.Logs)) // SentMessage event - require.Equal(constants.L2ToL2CrossDomainMessenger, sendMsgReceipt.Logs[0].Address) + require.Equal(predeploys.L2toL2CrossDomainMessengerAddr, sendMsgReceipt.Logs[0].Address) // Make sure supervisor syncs the chain A events sys.Supervisor.WaitForUnsafeHeadToAdvance(alice.ChainID(), 2) @@ -64,14 +64,14 @@ func TestRegularMessage(gt *testing.T) { txB := txintent.NewIntent[*txintent.RelayTrigger, *txintent.InteropOutput](bob.Plan()) txB.Content.DependOn(&txA.Result) idx := 0 - txB.Content.Fn(txintent.RelayIndexed(constants.L2ToL2CrossDomainMessenger, &txA.Result, &txA.PlannedTx.Included, idx)) + txB.Content.Fn(txintent.RelayIndexed(predeploys.L2toL2CrossDomainMessengerAddr, &txA.Result, &txA.PlannedTx.Included, idx)) relayMsgReceipt, err := txB.PlannedTx.Included.Eval(t.Ctx()) require.NoError(err, "relay msg receipt not found") // ExecutingMessage, EventLogger, RelayedMessage Events require.Equal(3, len(relayMsgReceipt.Logs)) - for logIdx, addr := range []common.Address{constants.CrossL2Inbox, eventLoggerAddress, constants.L2ToL2CrossDomainMessenger} { + for logIdx, addr := range []common.Address{predeploys.CrossL2InboxAddr, eventLoggerAddress, predeploys.L2toL2CrossDomainMessengerAddr} { require.Equal(addr, relayMsgReceipt.Logs[logIdx].Address) } // EventLogger topics and data diff --git a/op-acceptance-tests/tests/interop/loadtest/interop_load_test.go b/op-acceptance-tests/tests/interop/loadtest/interop_load_test.go index e1fd27cb5e812..e7de2a5fb9edb 100644 --- a/op-acceptance-tests/tests/interop/loadtest/interop_load_test.go +++ b/op-acceptance-tests/tests/interop/loadtest/interop_load_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" + "github.com/ethereum-optimism/optimism/op-core/predeploys" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-devstack/presets" @@ -58,7 +58,7 @@ type BlockRefByLabel interface { func planExecMsg(t devtest.T, initMsg *suptypes.Message, blockTime time.Duration, el BlockRefByLabel) txplan.Option { t.Require().NotNil(initMsg) return txplan.Combine(planCall(t, &txintent.ExecTrigger{ - Executor: constants.CrossL2Inbox, + Executor: predeploys.CrossL2InboxAddr, Msg: *initMsg, }), func(tx *txplan.PlannedTx) { tx.AgainstBlock.Wrap(func(fn plan.Fn[eth.BlockInfo]) plan.Fn[eth.BlockInfo] { diff --git a/op-acceptance-tests/tests/interop/loadtest/l2.go b/op-acceptance-tests/tests/interop/loadtest/l2.go index d76ab77275478..39a624b7fa4bf 100644 --- a/op-acceptance-tests/tests/interop/loadtest/l2.go +++ b/op-acceptance-tests/tests/interop/loadtest/l2.go @@ -4,10 +4,10 @@ import ( "sync/atomic" "time" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-service/txinclude" + "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" "github.com/ethereum-optimism/optimism/op-service/txplan" "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" diff --git a/op-acceptance-tests/tests/interop/message/interop_msg_test.go b/op-acceptance-tests/tests/interop/message/interop_msg_test.go index 2851792db1269..d8a4afe9a3a3a 100644 --- a/op-acceptance-tests/tests/interop/message/interop_msg_test.go +++ b/op-acceptance-tests/tests/interop/message/interop_msg_test.go @@ -7,7 +7,6 @@ import ( "testing" "time" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop" "github.com/ethereum-optimism/optimism/op-core/predeploys" "github.com/ethereum-optimism/optimism/op-devstack/devtest" @@ -265,7 +264,7 @@ func TestInitExecMultipleMsg(gt *testing.T) { interop.RandomInitTrigger(rng, eventLoggerAddress, 2, 13), } txA := txintent.NewIntent[*txintent.MultiTrigger, *txintent.InteropOutput](alice.Plan()) - txA.Content.Set(&txintent.MultiTrigger{Emitter: constants.MultiCall3, Calls: initCalls}) + txA.Content.Set(&txintent.MultiTrigger{Emitter: predeploys.MultiCall3Addr, Calls: initCalls}) // Trigger two events receiptA, err := txA.PlannedTx.Included.Eval(t.Ctx()) @@ -282,7 +281,7 @@ func TestInitExecMultipleMsg(gt *testing.T) { // Two events in tx so use every index indexes := []int{0, 1} - txB.Content.Fn(txintent.ExecuteIndexeds(constants.MultiCall3, constants.CrossL2Inbox, &txA.Result, indexes)) + txB.Content.Fn(txintent.ExecuteIndexeds(predeploys.MultiCall3Addr, predeploys.CrossL2InboxAddr, &txA.Result, indexes)) receiptB, err := txB.PlannedTx.Included.Eval(t.Ctx()) require.NoError(err) @@ -325,7 +324,7 @@ func TestExecSameMsgTwice(gt *testing.T) { // Single event in tx so indexes are 0, 0 indexes := []int{0, 0} - txB.Content.Fn(txintent.ExecuteIndexeds(constants.MultiCall3, constants.CrossL2Inbox, &txA.Result, indexes)) + txB.Content.Fn(txintent.ExecuteIndexeds(predeploys.MultiCall3Addr, predeploys.CrossL2InboxAddr, &txA.Result, indexes)) receiptB, err := txB.PlannedTx.Included.Eval(t.Ctx()) require.NoError(err) @@ -357,7 +356,7 @@ func TestExecDifferentTopicCount(gt *testing.T) { initCalls[topicCnt] = interop.RandomInitTrigger(rng, eventLoggerAddress, topicCnt, 10) } txA := txintent.NewIntent[*txintent.MultiTrigger, *txintent.InteropOutput](alice.Plan()) - txA.Content.Set(&txintent.MultiTrigger{Emitter: constants.MultiCall3, Calls: initCalls}) + txA.Content.Set(&txintent.MultiTrigger{Emitter: predeploys.MultiCall3Addr, Calls: initCalls}) // Trigger five events, each have {0, 1, 2, 3, 4} topics in it receiptA, err := txA.PlannedTx.Included.Eval(t.Ctx()) @@ -378,7 +377,7 @@ func TestExecDifferentTopicCount(gt *testing.T) { // Five events in tx so use every index indexes := []int{0, 1, 2, 3, 4} - txB.Content.Fn(txintent.ExecuteIndexeds(constants.MultiCall3, constants.CrossL2Inbox, &txA.Result, indexes)) + txB.Content.Fn(txintent.ExecuteIndexeds(predeploys.MultiCall3Addr, predeploys.CrossL2InboxAddr, &txA.Result, indexes)) receiptB, err := txB.PlannedTx.Included.Eval(t.Ctx()) require.NoError(err) @@ -410,7 +409,7 @@ func TestExecMsgOpaqueData(gt *testing.T) { initCalls[1] = largeInitTrigger txA := txintent.NewIntent[*txintent.MultiTrigger, *txintent.InteropOutput](alice.Plan()) - txA.Content.Set(&txintent.MultiTrigger{Emitter: constants.MultiCall3, Calls: initCalls}) + txA.Content.Set(&txintent.MultiTrigger{Emitter: predeploys.MultiCall3Addr, Calls: initCalls}) // Trigger two events receiptA, err := txA.PlannedTx.Included.Eval(t.Ctx()) @@ -429,7 +428,7 @@ func TestExecMsgOpaqueData(gt *testing.T) { // Two events in tx so use every index indexes := []int{0, 1} - txB.Content.Fn(txintent.ExecuteIndexeds(constants.MultiCall3, constants.CrossL2Inbox, &txA.Result, indexes)) + txB.Content.Fn(txintent.ExecuteIndexeds(predeploys.MultiCall3Addr, predeploys.CrossL2InboxAddr, &txA.Result, indexes)) receiptB, err := txB.PlannedTx.Included.Eval(t.Ctx()) require.NoError(err) @@ -461,7 +460,7 @@ func TestExecMsgDifferEventIndexInSingleTx(gt *testing.T) { } txA := txintent.NewIntent[*txintent.MultiTrigger, *txintent.InteropOutput](alice.Plan()) - txA.Content.Set(&txintent.MultiTrigger{Emitter: constants.MultiCall3, Calls: initCalls}) + txA.Content.Set(&txintent.MultiTrigger{Emitter: predeploys.MultiCall3Addr, Calls: initCalls}) // Trigger multiple events receiptA, err := txA.PlannedTx.Included.Eval(t.Ctx()) @@ -478,7 +477,7 @@ func TestExecMsgDifferEventIndexInSingleTx(gt *testing.T) { // first, random or last event of a tx. indexes := []int{0, 1 + rng.Intn(eventCnt-1), eventCnt - 1} - txB.Content.Fn(txintent.ExecuteIndexeds(constants.MultiCall3, constants.CrossL2Inbox, &txA.Result, indexes)) + txB.Content.Fn(txintent.ExecuteIndexeds(predeploys.MultiCall3Addr, predeploys.CrossL2InboxAddr, &txA.Result, indexes)) receiptB, err := txB.PlannedTx.Included.Eval(t.Ctx()) require.NoError(err) @@ -584,7 +583,7 @@ func TestExecMessageInvalidAttributes(gt *testing.T) { interop.RandomInitTrigger(rng, eventLoggerAddress, 1, 50), } txA := txintent.NewIntent[*txintent.MultiTrigger, *txintent.InteropOutput](alice.Plan()) - txA.Content.Set(&txintent.MultiTrigger{Emitter: constants.MultiCall3, Calls: initCalls}) + txA.Content.Set(&txintent.MultiTrigger{Emitter: predeploys.MultiCall3Addr, Calls: initCalls}) // Trigger multiple events receiptA, err := txA.PlannedTx.Included.Eval(t.Ctx()) @@ -611,7 +610,7 @@ func TestExecMessageInvalidAttributes(gt *testing.T) { // Random select event index in tx for injecting faults eventIdx := rng.Intn(len(initCalls)) - txC.Content.Fn(executeIndexedFault(constants.CrossL2Inbox, &txA.Result, eventIdx, rng, faults, chuck.ChainID())) + txC.Content.Fn(executeIndexedFault(predeploys.CrossL2InboxAddr, &txA.Result, eventIdx, rng, faults, chuck.ChainID())) // make sure that the transaction is not reverted by CrossL2Inbox... gas, err := txC.PlannedTx.Gas.Eval(t.Ctx()) @@ -633,7 +632,7 @@ func TestExecMessageInvalidAttributes(gt *testing.T) { // Three events in tx so use every index indexes := []int{0, 1, 2} - txB.Content.Fn(txintent.ExecuteIndexeds(constants.MultiCall3, constants.CrossL2Inbox, &txA.Result, indexes)) + txB.Content.Fn(txintent.ExecuteIndexeds(predeploys.MultiCall3Addr, predeploys.CrossL2InboxAddr, &txA.Result, indexes)) receiptB, err := txB.PlannedTx.Included.Eval(t.Ctx()) require.NoError(err) diff --git a/op-acceptance-tests/tests/interop/reorgs/init_exec_msg_test.go b/op-acceptance-tests/tests/interop/reorgs/init_exec_msg_test.go index d35b58ede0987..559caf9e4c087 100644 --- a/op-acceptance-tests/tests/interop/reorgs/init_exec_msg_test.go +++ b/op-acceptance-tests/tests/interop/reorgs/init_exec_msg_test.go @@ -5,9 +5,8 @@ import ( "testing" "time" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop" + "github.com/ethereum-optimism/optimism/op-core/predeploys" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-devstack/presets" @@ -15,6 +14,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/txintent" + "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" "github.com/ethereum-optimism/optimism/op-service/txplan" "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" "github.com/ethereum/go-ethereum/common" @@ -100,7 +100,7 @@ func TestReorgInitExecMsg(gt *testing.T) { execTx = txintent.NewIntent[*txintent.ExecTrigger, *txintent.InteropOutput](bob.Plan()) execTx.Content.DependOn(&initTx.Result) // single event in tx so index is 0. ExecuteIndexed returns a lambda to transform InteropOutput to a new ExecTrigger - execTx.Content.Fn(txintent.ExecuteIndexed(constants.CrossL2Inbox, &initTx.Result, 0)) + execTx.Content.Fn(txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &initTx.Result, 0)) var err error execReceipt, err = execTx.PlannedTx.Included.Eval(ctx) require.NoError(t, err) diff --git a/op-acceptance-tests/tests/interop/reorgs/invalid_exec_msgs_test.go b/op-acceptance-tests/tests/interop/reorgs/invalid_exec_msgs_test.go index d05dba3e091fd..0d6444e6fbf7f 100644 --- a/op-acceptance-tests/tests/interop/reorgs/invalid_exec_msgs_test.go +++ b/op-acceptance-tests/tests/interop/reorgs/invalid_exec_msgs_test.go @@ -7,14 +7,14 @@ import ( "testing" "time" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop" + "github.com/ethereum-optimism/optimism/op-core/predeploys" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/presets" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/txintent" + "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" "github.com/ethereum-optimism/optimism/op-service/txplan" suptypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" @@ -134,7 +134,7 @@ func testReorgInvalidExecMsg(gt *testing.T, txModifierFn func(msg *suptypes.Mess // modify the message in order to make it invalid txModifierFn(&msg) return &txintent.ExecTrigger{ - Executor: constants.CrossL2Inbox, + Executor: predeploys.CrossL2InboxAddr, Msg: msg, }, nil }) diff --git a/op-acceptance-tests/tests/interop/upgrade/pre_test.go b/op-acceptance-tests/tests/interop/upgrade/pre_test.go index e89ba2e0d0be3..ee4a3d566dd7f 100644 --- a/op-acceptance-tests/tests/interop/upgrade/pre_test.go +++ b/op-acceptance-tests/tests/interop/upgrade/pre_test.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum/go-ethereum/common" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-core/predeploys" @@ -82,7 +81,7 @@ func TestPreNoInbox(gt *testing.T) { // send executing message on chain B and confirm we got an error execTx := txintent.NewIntent[*txintent.ExecTrigger, *txintent.InteropOutput](bob.Plan()) execTx.Content.DependOn(&initMsg.Tx.Result) - execTx.Content.Fn(txintent.ExecuteIndexed(constants.CrossL2Inbox, &initMsg.Tx.Result, 0)) + execTx.Content.Fn(txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &initMsg.Tx.Result, 0)) execReceipt, err := execTx.PlannedTx.Included.Eval(sys.T.Ctx()) require.ErrorContains(err, "implementation not initialized", "error did not contain expected string") require.Nil(execReceipt) @@ -99,7 +98,7 @@ func TestPreNoInbox(gt *testing.T) { { ctx := sys.T.Ctx() - execTrigger, err := txintent.ExecuteIndexed(constants.CrossL2Inbox, &initMsg.Tx.Result, 0)(ctx) + execTrigger, err := txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &initMsg.Tx.Result, 0)(ctx) require.NoError(err) ed := stypes.ExecutingDescriptor{Timestamp: uint64(time.Now().Unix())} diff --git a/op-acceptance-tests/tests/isthmus/isthmus_test_helpers.go b/op-acceptance-tests/tests/isthmus/isthmus_test_helpers.go index 62330c92026e6..03b02df651ed9 100644 --- a/op-acceptance-tests/tests/isthmus/isthmus_test_helpers.go +++ b/op-acceptance-tests/tests/isthmus/isthmus_test_helpers.go @@ -2,15 +2,21 @@ package isthmus import ( "context" + "crypto/ecdsa" - "github.com/ethereum-optimism/optimism/devnet-sdk/system" "github.com/ethereum-optimism/optimism/op-service/retry" + "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-service/txplan" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm/program" ) -func DefaultTxSubmitOptions(w system.WalletV2) txplan.Option { +type walletV2 interface { + PrivateKey() *ecdsa.PrivateKey + Client() *sources.EthClient +} + +func DefaultTxSubmitOptions(w walletV2) txplan.Option { return txplan.Combine( txplan.WithPrivateKey(w.PrivateKey()), txplan.WithChainID(w.Client()), @@ -21,21 +27,21 @@ func DefaultTxSubmitOptions(w system.WalletV2) txplan.Option { ) } -func DefaultTxInclusionOptions(w system.WalletV2) txplan.Option { +func DefaultTxInclusionOptions(w walletV2) txplan.Option { return txplan.Combine( txplan.WithRetryInclusion(w.Client(), 10, retry.Exponential()), txplan.WithBlockInclusionInfo(w.Client()), ) } -func DefaultTxOpts(w system.WalletV2) txplan.Option { +func DefaultTxOpts(w walletV2) txplan.Option { return txplan.Combine( DefaultTxSubmitOptions(w), DefaultTxInclusionOptions(w), ) } -func DeployProgram(ctx context.Context, wallet system.WalletV2, code []byte) (common.Address, error) { +func DeployProgram(ctx context.Context, wallet walletV2, code []byte) (common.Address, error) { deployProgram := program.New().ReturnViaCodeCopy(code) opts := DefaultTxOpts(wallet) diff --git a/op-challenger/README.md b/op-challenger/README.md index cc6af5a908e57..4961daff30b6d 100644 --- a/op-challenger/README.md +++ b/op-challenger/README.md @@ -21,12 +21,8 @@ accessed by running `./op-challenger --help`. ### Running with Cannon on Local Devnet -To run `op-challenger` against the local devnet, first clean and run -the devnet. From the root of the repository run: - -```shell -cd kurtosis-devnet && just simple-devnet -``` +To run `op-challenger` against a local devnet, first start a local devnet +that exposes the `simple-devnet` enclave. Then build the `op-challenger` with `make op-challenger`. diff --git a/op-deployer/pkg/deployer/apply.go b/op-deployer/pkg/deployer/apply.go index b4df32a3b4062..7a81788180bfc 100644 --- a/op-deployer/pkg/deployer/apply.go +++ b/op-deployer/pkg/deployer/apply.go @@ -10,7 +10,6 @@ import ( "github.com/ethereum-optimism/optimism/op-service/ioutil" - "github.com/ethereum-optimism/optimism/devnet-sdk/proofs/prestate" "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" "github.com/ethereum-optimism/optimism/op-chain-ops/script" "github.com/ethereum-optimism/optimism/op-chain-ops/script/forking" @@ -27,6 +26,7 @@ import ( opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum-optimism/optimism/op-service/prestate" "github.com/ethereum-optimism/optimism/op-validator/pkg/service" "github.com/ethereum-optimism/optimism/op-validator/pkg/validations" "github.com/ethereum/go-ethereum/common" diff --git a/op-deployer/pkg/deployer/pipeline/pre_state.go b/op-deployer/pkg/deployer/pipeline/pre_state.go index cb41f5041a2f0..ed8769f1d638e 100644 --- a/op-deployer/pkg/deployer/pipeline/pre_state.go +++ b/op-deployer/pkg/deployer/pipeline/pre_state.go @@ -6,8 +6,8 @@ import ( "encoding/json" "fmt" - "github.com/ethereum-optimism/optimism/devnet-sdk/proofs/prestate" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum-optimism/optimism/op-service/prestate" ) type PreStateBuilder interface { diff --git a/op-deployer/pkg/deployer/state/state.go b/op-deployer/pkg/deployer/state/state.go index cf86c0fa85b8b..40f31c5f3f1b8 100644 --- a/op-deployer/pkg/deployer/state/state.go +++ b/op-deployer/pkg/deployer/state/state.go @@ -5,8 +5,8 @@ import ( "github.com/ethereum/go-ethereum/core" - "github.com/ethereum-optimism/optimism/devnet-sdk/proofs/prestate" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + "github.com/ethereum-optimism/optimism/op-service/prestate" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" "github.com/ethereum-optimism/optimism/op-chain-ops/addresses" diff --git a/op-deployer/pkg/deployer/validate/helpers_test.go b/op-deployer/pkg/deployer/validate/helpers_test.go index a9c893f94ea6a..c0133298681cf 100644 --- a/op-deployer/pkg/deployer/validate/helpers_test.go +++ b/op-deployer/pkg/deployer/validate/helpers_test.go @@ -4,11 +4,11 @@ import ( "context" "testing" - "github.com/ethereum-optimism/optimism/devnet-sdk/proofs/prestate" "github.com/ethereum-optimism/optimism/op-chain-ops/addresses" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum-optimism/optimism/op-service/prestate" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" diff --git a/op-devstack/devtest/testing.go b/op-devstack/devtest/testing.go index f368c0ef16f7d..6f4c0e0dcc186 100644 --- a/op-devstack/devtest/testing.go +++ b/op-devstack/devtest/testing.go @@ -15,7 +15,6 @@ import ( "github.com/ethereum/go-ethereum/log" - "github.com/ethereum-optimism/optimism/devnet-sdk/telemetry" "github.com/ethereum-optimism/optimism/op-service/log/logfilter" "github.com/ethereum-optimism/optimism/op-service/logmods" "github.com/ethereum-optimism/optimism/op-service/testlog" @@ -313,7 +312,7 @@ func SerialT(t *testing.T) T { // Set the lowest default log-level, so the log-filters on top can apply correctly logger := testlog.LoggerWithHandlerMod(t, log.LevelTrace, - telemetry.WrapHandler, logfilter.WrapFilterHandler, logfilter.WrapContextHandler) + wrapTracingHandler, logfilter.WrapFilterHandler, logfilter.WrapContextHandler) h, ok := logmods.FindHandler[logfilter.FilterHandler](logger.Handler()) if ok { // Apply default log level. This may be overridden later. diff --git a/op-devstack/devtest/tracing_handler.go b/op-devstack/devtest/tracing_handler.go new file mode 100644 index 0000000000000..3918d7505d0b8 --- /dev/null +++ b/op-devstack/devtest/tracing_handler.go @@ -0,0 +1,90 @@ +package devtest + +import ( + "context" + "fmt" + "log/slog" + + "github.com/ethereum-optimism/optimism/op-service/logmods" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +func wrapTracingHandler(h slog.Handler) slog.Handler { + return &tracingHandler{Handler: h} +} + +type tracingHandler struct { + slog.Handler +} + +var _ logmods.Handler = (*tracingHandler)(nil) + +func (h *tracingHandler) Unwrap() slog.Handler { + return h.Handler +} + +func (h *tracingHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return &tracingHandler{Handler: h.Handler.WithAttrs(attrs)} +} + +func (h *tracingHandler) WithGroup(name string) slog.Handler { + return &tracingHandler{Handler: h.Handler.WithGroup(name)} +} + +func (h *tracingHandler) Handle(ctx context.Context, record slog.Record) error { + span := trace.SpanFromContext(ctx) + if span.IsRecording() { + recorder := &traceAttrAccumulator{} + record.Attrs(func(attr slog.Attr) bool { + recorder.register(attr) + return true + }) + span.AddEvent(record.Message, trace.WithAttributes(recorder.kv...)) + } + + spanCtx := trace.SpanContextFromContext(ctx) + if spanCtx.HasTraceID() { + record.AddAttrs(slog.String("trace_id", spanCtx.TraceID().String())) + } + if spanCtx.HasSpanID() { + record.AddAttrs(slog.String("span_id", spanCtx.SpanID().String())) + } + return h.Handler.Handle(ctx, record) +} + +type traceAttrAccumulator struct { + kv []attribute.KeyValue +} + +func (ac *traceAttrAccumulator) register(attr slog.Attr) { + switch attr.Value.Kind() { + case slog.KindAny: + ac.kv = append(ac.kv, attribute.String(attr.Key, fmt.Sprintf("%v", attr.Value.Any()))) + case slog.KindBool: + ac.kv = append(ac.kv, attribute.Bool(attr.Key, attr.Value.Bool())) + case slog.KindDuration: + ac.kv = append(ac.kv, attribute.String(attr.Key, attr.Value.Duration().String())) + case slog.KindFloat64: + ac.kv = append(ac.kv, attribute.Float64(attr.Key, attr.Value.Float64())) + case slog.KindInt64: + ac.kv = append(ac.kv, attribute.Int64(attr.Key, attr.Value.Int64())) + case slog.KindString: + ac.kv = append(ac.kv, attribute.String(attr.Key, attr.Value.String())) + case slog.KindTime: + ac.kv = append(ac.kv, attribute.String(attr.Key, attr.Value.Time().String())) + case slog.KindUint64: + val := attr.Value.Uint64() + ac.kv = append(ac.kv, attribute.Int64(attr.Key, int64(val))) + if val > uint64(1<<63-1) { + ac.kv = append(ac.kv, attribute.Bool(attr.Key+".overflow", true)) + ac.kv = append(ac.kv, attribute.String(attr.Key+".actual", fmt.Sprintf("%d", val))) + } + case slog.KindGroup: + for _, groupAttr := range attr.Value.Group() { + ac.register(groupAttr) + } + case slog.KindLogValuer: + ac.register(slog.Attr{Key: attr.Key, Value: attr.Value.LogValuer().LogValue()}) + } +} diff --git a/op-devstack/dsl/eoa.go b/op-devstack/dsl/eoa.go index 99a35df815dcd..d23499e89fedb 100644 --- a/op-devstack/dsl/eoa.go +++ b/op-devstack/dsl/eoa.go @@ -7,9 +7,8 @@ import ( "math/rand" "time" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop" + "github.com/ethereum-optimism/optimism/op-core/predeploys" e2eBindings "github.com/ethereum-optimism/optimism/op-e2e/bindings" "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum-optimism/optimism/op-service/eth" @@ -233,7 +232,7 @@ func (u *EOA) WaitForBalance(v eth.ETH) { } func (u *EOA) DeployEventLogger() common.Address { - tx := txplan.NewPlannedTx(u.Plan(), txplan.WithData(common.FromHex(bindings.EventloggerBin))) + tx := txplan.NewPlannedTx(u.Plan(), txplan.WithData(common.FromHex(txIntentBindings.EventloggerBin))) res, err := tx.Included.Eval(u.ctx) u.t.Require().NoError(err, "failed to deploy EventLogger") eventLoggerAddress := res.ContractAddress @@ -292,7 +291,7 @@ func (u *EOA) SendRandomInitMessage(rng *rand.Rand, eventLoggerAddress common.Ad func (u *EOA) SendExecMessage(initMsg *InitMessage) *ExecMessage { tx := txintent.NewIntent[*txintent.ExecTrigger, *txintent.InteropOutput](u.Plan()) tx.Content.DependOn(&initMsg.Tx.Result) - tx.Content.Fn(txintent.ExecuteIndexed(constants.CrossL2Inbox, &initMsg.Tx.Result, 0)) + tx.Content.Fn(txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &initMsg.Tx.Result, 0)) receipt, err := tx.PlannedTx.Included.Eval(u.ctx) u.t.Require().NoError(err, "exec msg receipt not found") u.log.Info("exec message included", "chain", u.ChainID(), "block", receipt.BlockNumber) @@ -318,7 +317,7 @@ func (u *EOA) SendInvalidExecMessage(initMsg *InitMessage) *ExecMessage { // Create the exec trigger with the invalid message execTrigger := &txintent.ExecTrigger{ - Executor: constants.CrossL2Inbox, + Executor: predeploys.CrossL2InboxAddr, Msg: msg, } @@ -348,7 +347,7 @@ func (u *EOA) SendPackedRandomInitMessages(rng *rand.Rand, eventLoggerAddress co initCalls[index] = interop.RandomInitTrigger(rng, eventLoggerAddress, rng.Intn(5), rng.Intn(100)) } tx := txintent.NewIntent[*txintent.MultiTrigger, *txintent.InteropOutput](u.Plan()) - tx.Content.Set(&txintent.MultiTrigger{Emitter: constants.MultiCall3, Calls: initCalls}) + tx.Content.Set(&txintent.MultiTrigger{Emitter: predeploys.MultiCall3Addr, Calls: initCalls}) receipt, err := tx.PlannedTx.Included.Eval(u.ctx) if err != nil { return nil, nil, err @@ -369,7 +368,7 @@ func (u *EOA) SendPackedExecMessages(dependOn *txintent.IntentTx[*txintent.Multi for idx := range len(result.Entries) { indexes = append(indexes, idx) } - tx.Content.Fn(txintent.ExecuteIndexeds(constants.MultiCall3, constants.CrossL2Inbox, &dependOn.Result, indexes)) + tx.Content.Fn(txintent.ExecuteIndexeds(predeploys.MultiCall3Addr, predeploys.CrossL2InboxAddr, &dependOn.Result, indexes)) receipt, err := tx.PlannedTx.Included.Eval(u.ctx) if err != nil { return nil, nil, err @@ -513,7 +512,7 @@ func (p *SameTimestampPair) SubmitInit() *txplan.PlannedTx { func (p *SameTimestampPair) SubmitExecTo(executor *EOA) *txplan.PlannedTx { tx := txintent.NewIntent[*txintent.ExecTrigger, *txintent.InteropOutput](executor.Plan()) tx.Content.Set(&txintent.ExecTrigger{ - Executor: constants.CrossL2Inbox, + Executor: predeploys.CrossL2InboxAddr, Msg: p.Message, }) _, err := tx.PlannedTx.Submitted.Eval(executor.ctx) @@ -529,7 +528,7 @@ func (p *SameTimestampPair) SubmitInvalidExecTo(executor *EOA) *txplan.PlannedTx tx := txintent.NewIntent[*txintent.ExecTrigger, *txintent.InteropOutput](executor.Plan()) tx.Content.Set(&txintent.ExecTrigger{ - Executor: constants.CrossL2Inbox, + Executor: predeploys.CrossL2InboxAddr, Msg: invalidMsg, }) _, err := tx.PlannedTx.Submitted.Eval(executor.ctx) diff --git a/op-e2e/actions/interop/interop_msg_test.go b/op-e2e/actions/interop/interop_msg_test.go index 4faf502c8fb1d..abf9ed81d65bf 100644 --- a/op-e2e/actions/interop/interop_msg_test.go +++ b/op-e2e/actions/interop/interop_msg_test.go @@ -6,15 +6,15 @@ import ( "math/rand" "testing" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop" + "github.com/ethereum-optimism/optimism/op-core/predeploys" "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" "github.com/ethereum-optimism/optimism/op-e2e/actions/interop/dsl" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum-optimism/optimism/op-service/txintent" + "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" "github.com/ethereum-optimism/optimism/op-service/txplan" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -449,7 +449,7 @@ func TestInitAndExecMsgSameTimestamp(gt *testing.T) { txB.Content.DependOn(&txA.Result) // Single event in tx so index is 0 - txB.Content.Fn(txintent.ExecuteIndexed(constants.CrossL2Inbox, &txA.Result, 0)) + txB.Content.Fn(txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &txA.Result, 0)) actors.ChainB.Sequencer.ActL2StartBlock(t) _, err = txB.PlannedTx.Included.Eval(t.Ctx()) @@ -509,7 +509,7 @@ func TestBreakTimestampInvariant(gt *testing.T) { txB.Content.DependOn(&txA.Result) // Single event in tx so index is 0 - txB.Content.Fn(txintent.ExecuteIndexed(constants.CrossL2Inbox, &txA.Result, 0)) + txB.Content.Fn(txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &txA.Result, 0)) actors.ChainB.Sequencer.ActL2StartBlock(t) _, err = txB.PlannedTx.Included.Eval(t.Ctx()) @@ -636,7 +636,7 @@ func TestExecMsgDifferTxIndex(gt *testing.T) { execTx := txintent.NewIntent[*txintent.ExecTrigger, *txintent.InteropOutput](optsB) // Single event in every tx so index is always 0 - execTx.Content.Fn(txintent.ExecuteIndexed(constants.CrossL2Inbox, &initTx.Result, 0)) + execTx.Content.Fn(txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &initTx.Result, 0)) execTx.Content.DependOn(&initTx.Result) includedBlock, err := execTx.PlannedTx.IncludedBlock.Eval(t.Ctx()) @@ -710,7 +710,7 @@ func TestExpiredMessage(gt *testing.T) { txB.Content.DependOn(&txA.Result) // Single event in tx so index is 0 - txB.Content.Fn(txintent.ExecuteIndexed(constants.CrossL2Inbox, &txA.Result, 0)) + txB.Content.Fn(txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &txA.Result, 0)) actors.ChainB.Sequencer.ActL2StartBlock(t) _, err = txB.PlannedTx.Included.Eval(t.Ctx()) @@ -783,7 +783,7 @@ func TestCrossPatternSameTimestamp(gt *testing.T) { // Intent to execute message X on chain B tx1 := txintent.NewIntent[*txintent.ExecTrigger, *txintent.InteropOutput](optsB) - tx1.Content.Fn(txintent.ExecuteIndexed(constants.CrossL2Inbox, &tx0.Result, 0)) + tx1.Content.Fn(txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &tx0.Result, 0)) tx1.Content.DependOn(&tx0.Result) _, err = tx1.PlannedTx.Submitted.Eval(t.Ctx()) @@ -805,7 +805,7 @@ func TestCrossPatternSameTimestamp(gt *testing.T) { // override nonce optsA = txplan.Combine(optsA, txplan.WithStaticNonce(nonce+1)) tx3 := txintent.NewIntent[*txintent.ExecTrigger, *txintent.InteropOutput](optsA) - tx3.Content.Fn(txintent.ExecuteIndexed(constants.CrossL2Inbox, &tx2.Result, 0)) + tx3.Content.Fn(txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &tx2.Result, 0)) tx3.Content.DependOn(&tx2.Result) _, err = tx3.PlannedTx.Submitted.Eval(t.Ctx()) @@ -887,7 +887,7 @@ func ExecTriggerFromInitTrigger(init *txintent.InitTrigger, logIndex uint, targe if x := len(output.Entries); x <= int(logIndex) { return nil, fmt.Errorf("invalid index: %d, only have %d events", logIndex, x) } - return &txintent.ExecTrigger{Executor: constants.CrossL2Inbox, Msg: output.Entries[logIndex]}, nil + return &txintent.ExecTrigger{Executor: predeploys.CrossL2InboxAddr, Msg: output.Entries[logIndex]}, nil } // TestCrossPatternSameTx tests below scenario: @@ -940,10 +940,10 @@ func TestCrossPatternSameTx(gt *testing.T) { // Intent to initiate message X and execute message Y at chain A txA := txintent.NewIntent[*txintent.MultiTrigger, *txintent.InteropOutput](optsA) - txA.Content.Set(&txintent.MultiTrigger{Emitter: constants.MultiCall3, Calls: callsA}) + txA.Content.Set(&txintent.MultiTrigger{Emitter: predeploys.MultiCall3Addr, Calls: callsA}) // Intent to initiate message Y and execute message X at chain B txB := txintent.NewIntent[*txintent.MultiTrigger, *txintent.InteropOutput](optsB) - txB.Content.Set(&txintent.MultiTrigger{Emitter: constants.MultiCall3, Calls: callsB}) + txB.Content.Set(&txintent.MultiTrigger{Emitter: predeploys.MultiCall3Addr, Calls: callsB}) includedA, err := txA.PlannedTx.IncludedBlock.Eval(t.Ctx()) require.NoError(t, err) @@ -962,14 +962,14 @@ func TestCrossPatternSameTx(gt *testing.T) { // confirm speculatively built exec message X by rebuilding after txA inclusion _, err = txA.Result.Eval(t.Ctx()) require.NoError(t, err) - multiTriggerA, err := txintent.ExecuteIndexeds(constants.MultiCall3, constants.CrossL2Inbox, &txA.Result, []int{int(logIndexX)})(t.Ctx()) + multiTriggerA, err := txintent.ExecuteIndexeds(predeploys.MultiCall3Addr, predeploys.CrossL2InboxAddr, &txA.Result, []int{int(logIndexX)})(t.Ctx()) require.NoError(t, err) require.Equal(t, multiTriggerA.Calls[logIndexX], execX) // confirm speculatively built exec message Y by rebuilding after txB inclusion _, err = txB.Result.Eval(t.Ctx()) require.NoError(t, err) - multiTriggerB, err := txintent.ExecuteIndexeds(constants.MultiCall3, constants.CrossL2Inbox, &txB.Result, []int{int(logIndexY)})(t.Ctx()) + multiTriggerB, err := txintent.ExecuteIndexeds(predeploys.MultiCall3Addr, predeploys.CrossL2InboxAddr, &txB.Result, []int{int(logIndexY)})(t.Ctx()) require.NoError(t, err) require.Equal(t, multiTriggerB.Calls[logIndexY], execY) @@ -1021,7 +1021,7 @@ func TestCycleInTx(gt *testing.T) { // Intent to execute message X and initiate message X at chain A tx := txintent.NewIntent[*txintent.MultiTrigger, *txintent.InteropOutput](optsA) - tx.Content.Set(&txintent.MultiTrigger{Emitter: constants.MultiCall3, Calls: calls}) + tx.Content.Set(&txintent.MultiTrigger{Emitter: predeploys.MultiCall3Addr, Calls: calls}) included, err := tx.PlannedTx.IncludedBlock.Eval(t.Ctx()) require.NoError(t, err) @@ -1033,7 +1033,7 @@ func TestCycleInTx(gt *testing.T) { // confirm speculatively built exec message by rebuilding after tx inclusion _, err = tx.Result.Eval(t.Ctx()) require.NoError(t, err) - exec2, err := txintent.ExecuteIndexed(constants.CrossL2Inbox, &tx.Result, int(logIndexX))(t.Ctx()) + exec2, err := txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &tx.Result, int(logIndexX))(t.Ctx()) require.NoError(t, err) require.Equal(t, exec2, exec) @@ -1129,7 +1129,7 @@ func TestCycleInBlock(gt *testing.T) { _, err = tx.Result.Eval(t.Ctx()) require.NoError(t, err) // log index is 0 because tx emitted a single log - exec2, err := txintent.ExecuteIndexed(constants.CrossL2Inbox, &tx.Result, 0)(t.Ctx()) + exec2, err := txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &tx.Result, 0)(t.Ctx()) require.NoError(t, err) require.Equal(t, exec2, exec) @@ -1224,14 +1224,14 @@ func TestCycleAcrossChainsSameTimestamp(gt *testing.T) { _, err = tx2.Result.Eval(t.Ctx()) require.NoError(t, err) // log index is 0 because tx emitted a single log - execX2, err := txintent.ExecuteIndexed(constants.CrossL2Inbox, &tx2.Result, 0)(t.Ctx()) + execX2, err := txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &tx2.Result, 0)(t.Ctx()) require.NoError(t, err) require.Equal(t, execX2, execX) tx3 := intents[3] _, err = tx3.Result.Eval(t.Ctx()) require.NoError(t, err) // log index is 0 because tx emitted a single log - execY2, err := txintent.ExecuteIndexed(constants.CrossL2Inbox, &tx3.Result, 0)(t.Ctx()) + execY2, err := txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &tx3.Result, 0)(t.Ctx()) require.NoError(t, err) require.Equal(t, execY2, execY) @@ -1288,10 +1288,10 @@ func TestCycleAcrossChainsSameTx(gt *testing.T) { // tx0 executes message X, then initiates message Y tx0 := txintent.NewIntent[*txintent.MultiTrigger, *txintent.InteropOutput](optsA) - tx0.Content.Set(&txintent.MultiTrigger{Emitter: constants.MultiCall3, Calls: []txintent.Call{execX, initY}}) + tx0.Content.Set(&txintent.MultiTrigger{Emitter: predeploys.MultiCall3Addr, Calls: []txintent.Call{execX, initY}}) // tx1 executes message Y, then initiates message X tx1 := txintent.NewIntent[*txintent.MultiTrigger, *txintent.InteropOutput](optsB) - tx1.Content.Set(&txintent.MultiTrigger{Emitter: constants.MultiCall3, Calls: []txintent.Call{execY, initX}}) + tx1.Content.Set(&txintent.MultiTrigger{Emitter: predeploys.MultiCall3Addr, Calls: []txintent.Call{execY, initX}}) included0, err := tx0.PlannedTx.IncludedBlock.Eval(t.Ctx()) require.NoError(t, err) @@ -1307,12 +1307,12 @@ func TestCycleAcrossChainsSameTx(gt *testing.T) { // confirm speculatively built exec message by rebuilding after tx inclusion _, err = tx0.Result.Eval(t.Ctx()) require.NoError(t, err) - execY2, err := txintent.ExecuteIndexed(constants.CrossL2Inbox, &tx0.Result, int(logIndexY))(t.Ctx()) + execY2, err := txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &tx0.Result, int(logIndexY))(t.Ctx()) require.NoError(t, err) require.Equal(t, execY2, execY) _, err = tx1.Result.Eval(t.Ctx()) require.NoError(t, err) - execX2, err := txintent.ExecuteIndexed(constants.CrossL2Inbox, &tx1.Result, int(logIndexX))(t.Ctx()) + execX2, err := txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &tx1.Result, int(logIndexX))(t.Ctx()) require.NoError(t, err) require.Equal(t, execX2, execX) @@ -1343,7 +1343,7 @@ func TestExecMsgPointToSelf(gt *testing.T) { // manually construct identifier which makes exec message point to itself identifier := suptypes.Identifier{ - Origin: constants.CrossL2Inbox, + Origin: predeploys.CrossL2InboxAddr, BlockNumber: targetNum, LogIndex: uint32(0), // tx will emit single ExecutingMessage event to set log index as 0 Timestamp: targetTime, @@ -1354,7 +1354,7 @@ func TestExecMsgPointToSelf(gt *testing.T) { payloadHash := testutils.RandomHash(rng) message := suptypes.Message{Identifier: identifier, PayloadHash: payloadHash} - exec := &txintent.ExecTrigger{Executor: constants.CrossL2Inbox, Msg: message} + exec := &txintent.ExecTrigger{Executor: predeploys.CrossL2InboxAddr, Msg: message} // txintent for executing message pointing to itself tx := txintent.NewIntent[*txintent.ExecTrigger, *txintent.InteropOutput](optsA) tx.Content.Set(exec) diff --git a/devnet-sdk/proofs/prestate/client.go b/op-service/prestate/client.go similarity index 73% rename from devnet-sdk/proofs/prestate/client.go rename to op-service/prestate/client.go index a5a6e7b4c6aba..42708dfbd190a 100644 --- a/devnet-sdk/proofs/prestate/client.go +++ b/op-service/prestate/client.go @@ -14,23 +14,23 @@ import ( "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" ) -// These constants should be in sync with op-program/chainconfig/chaincfg.go +// These constants should be in sync with op-program/chainconfig/chaincfg.go. const ( InteropDepSetName = "depsets.json" rollupConfigSuffix = "-rollup.json" genensisConfigSuffix = "-genesis-l2.json" ) -// PrestateManifest maps prestate identifiers to their hashes +// PrestateManifest maps prestate identifiers to their hashes. type PrestateManifest map[string]string -// PrestateBuilderClient is a client for the prestate builder service +// PrestateBuilderClient is a client for the prestate builder service. type PrestateBuilderClient struct { url string client *http.Client } -// NewPrestateBuilderClient creates a new client for the prestate builder service +// NewPrestateBuilderClient creates a new client for the prestate builder service. func NewPrestateBuilderClient(url string) *PrestateBuilderClient { return &PrestateBuilderClient{ url: url, @@ -38,24 +38,23 @@ func NewPrestateBuilderClient(url string) *PrestateBuilderClient { } } -// FileInput represents a file to be used in the build process +// FileInput represents a file to be used in the build process. type FileInput struct { - Name string // Name of the file (used for identification) - Content io.Reader // Content of the file - Type string // Type information (e.g., "rollup-config", "genesis-config", "interop") + Name string + Content io.Reader + Type string } -// buildContext holds all the inputs for a build operation type buildContext struct { chains []string files []FileInput generatedInteropDepSet bool } -// PrestateBuilderOption is a functional option for configuring a build +// PrestateBuilderOption is a functional option for configuring a build. type PrestateBuilderOption func(*buildContext) -// WithInteropDepSet adds an interop dependency set file to the build +// WithInteropDepSet adds an interop dependency set file to the build. func WithInteropDepSet(content io.Reader) PrestateBuilderOption { return func(c *buildContext) { c.files = append(c.files, FileInput{ @@ -81,31 +80,32 @@ func generateInteropDepSet(chains []string) ([]byte, error) { return nil, fmt.Errorf("failed to create interop dependency set: %w", err) } - json, err := json.Marshal(interopDepSet) + jsonBytes, err := json.Marshal(interopDepSet) if err != nil { return nil, err } - return json, nil + return jsonBytes, nil } +// WithGeneratedInteropDepSet requests generation of the interop dependency set from the chain list. func WithGeneratedInteropDepSet() PrestateBuilderOption { return func(c *buildContext) { c.generatedInteropDepSet = true } } -// WithChainConfig adds a pair of rollup and genesis config files to the build -func WithChainConfig(chainId string, rollupContent io.Reader, genesisContent io.Reader) PrestateBuilderOption { +// WithChainConfig adds a pair of rollup and genesis config files to the build. +func WithChainConfig(chainID string, rollupContent io.Reader, genesisContent io.Reader) PrestateBuilderOption { return func(c *buildContext) { - c.chains = append(c.chains, chainId) + c.chains = append(c.chains, chainID) c.files = append(c.files, FileInput{ - Name: chainId + rollupConfigSuffix, + Name: chainID + rollupConfigSuffix, Content: rollupContent, Type: "rollup-config", }, FileInput{ - Name: chainId + genensisConfigSuffix, + Name: chainID + genensisConfigSuffix, Content: genesisContent, Type: "genesis-config", }, @@ -113,15 +113,13 @@ func WithChainConfig(chainId string, rollupContent io.Reader, genesisContent io. } } -// BuildPrestate sends the files to the prestate builder service and returns a manifest of the built prestates +// BuildPrestate sends the files to the prestate builder service and returns a manifest of the built prestates. func (c *PrestateBuilderClient) BuildPrestate(ctx context.Context, opts ...PrestateBuilderOption) (PrestateManifest, error) { fmt.Println("Starting prestate build...") - // Apply options to build context bc := &buildContext{ files: []FileInput{}, } - for _, opt := range opts { opt(bc) } @@ -140,57 +138,42 @@ func (c *PrestateBuilderClient) BuildPrestate(ctx context.Context, opts ...Prest fmt.Printf("Preparing to upload %d files\n", len(bc.files)) - // Create a multipart form - var b bytes.Buffer - w := multipart.NewWriter(&b) - - // Add each file to the form + var body bytes.Buffer + writer := multipart.NewWriter(&body) for _, file := range bc.files { fmt.Printf("Adding file to form: %s (type: %s)\n", file.Name, file.Type) - // Create a form file with the file's name - fw, err := w.CreateFormFile("files[]", filepath.Base(file.Name)) + formFile, err := writer.CreateFormFile("files[]", filepath.Base(file.Name)) if err != nil { return nil, fmt.Errorf("failed to create form file: %w", err) } - - // Copy the file content to the form - if _, err := io.Copy(fw, file.Content); err != nil { + if _, err := io.Copy(formFile, file.Content); err != nil { return nil, fmt.Errorf("failed to copy file content: %w", err) } } - - // Close the multipart writer - if err := w.Close(); err != nil { + if err := writer.Close(); err != nil { return nil, fmt.Errorf("failed to close multipart writer: %w", err) } fmt.Printf("Sending build request to %s\n", c.url) - - // Create the HTTP request - req, err := http.NewRequestWithContext(ctx, "POST", c.url, &b) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.url, &body) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } + req.Header.Set("Content-Type", writer.FormDataContentType()) - // Set the content type - req.Header.Set("Content-Type", w.FormDataContentType()) - - // Send the request resp, err := c.client.Do(req) if err != nil { return nil, fmt.Errorf("failed to send request: %w", err) } defer resp.Body.Close() - // Check the response status if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNotModified { - body, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body)) + respBody, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(respBody)) } fmt.Println("Build request successful, fetching build manifest...") - // If the build was successful, get the info.json file infoURL := c.url if infoURL[len(infoURL)-1] != '/' { infoURL += "/" @@ -198,8 +181,7 @@ func (c *PrestateBuilderClient) BuildPrestate(ctx context.Context, opts ...Prest infoURL += "info.json" fmt.Printf("Requesting manifest from %s\n", infoURL) - - infoReq, err := http.NewRequestWithContext(ctx, "GET", infoURL, nil) + infoReq, err := http.NewRequestWithContext(ctx, http.MethodGet, infoURL, nil) if err != nil { return nil, fmt.Errorf("failed to create info request: %w", err) } @@ -211,18 +193,15 @@ func (c *PrestateBuilderClient) BuildPrestate(ctx context.Context, opts ...Prest defer infoResp.Body.Close() if infoResp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(infoResp.Body) - return nil, fmt.Errorf("unexpected info.json status code: %d, body: %s", infoResp.StatusCode, string(body)) + respBody, _ := io.ReadAll(infoResp.Body) + return nil, fmt.Errorf("unexpected info.json status code: %d, body: %s", infoResp.StatusCode, string(respBody)) } - // Parse the JSON response var manifest PrestateManifest if err := json.NewDecoder(infoResp.Body).Decode(&manifest); err != nil { return nil, fmt.Errorf("failed to decode info.json response: %w", err) } fmt.Printf("Build complete. Generated %d prestate entries\n", len(manifest)) - return manifest, nil - } diff --git a/op-service/testutils/devnet/prestatebuilder.go b/op-service/testutils/devnet/prestatebuilder.go index a9127e12aaaf2..fc1fda07b9e04 100644 --- a/op-service/testutils/devnet/prestatebuilder.go +++ b/op-service/testutils/devnet/prestatebuilder.go @@ -3,7 +3,7 @@ package devnet import ( "context" - "github.com/ethereum-optimism/optimism/devnet-sdk/proofs/prestate" + "github.com/ethereum-optimism/optimism/op-service/prestate" ) type mockPreStateBuilder struct { diff --git a/op-service/txintent/bindings/eventlogger_deploy.go b/op-service/txintent/bindings/eventlogger_deploy.go new file mode 100644 index 0000000000000..1f6720111a6bc --- /dev/null +++ b/op-service/txintent/bindings/eventlogger_deploy.go @@ -0,0 +1,5 @@ +package bindings + +// EventloggerBin is the compiled EventLogger bytecode used by interop tests and devstack helpers. +// It was originally generated from packages/contracts-bedrock/src/integration/EventLogger.sol. +var EventloggerBin = "0x6080604052348015600e575f80fd5b506102ac8061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c8063ab4d6f7514610038578063edebc13b1461004d575b5f80fd5b61004b61004636600461013e565b610060565b005b61004b61005b36600461016c565b6100bd565b60405163ab4d6f7560e01b81526022602160991b019063ab4d6f759061008c9085908590600401610226565b5f604051808303815f87803b1580156100a3575f80fd5b505af11580156100b5573d5f803e3d5ffd5b505050505050565b80604051818482378486356020880135604089013560608a0135848015610102576001811461010a5760028114610113576003811461011d5760048114610128575f80fd5b8787a0610130565b848888a1610130565b83858989a2610130565b8284868a8aa3610130565b818385878b8ba45b505050505050505050505050565b5f8082840360c0811215610150575f80fd5b60a081121561015d575f80fd5b50919360a08501359350915050565b5f805f806040858703121561017f575f80fd5b843567ffffffffffffffff80821115610196575f80fd5b818701915087601f8301126101a9575f80fd5b8135818111156101b7575f80fd5b8860208260051b85010111156101cb575f80fd5b6020928301965094509086013590808211156101e5575f80fd5b818701915087601f8301126101f8575f80fd5b813581811115610206575f80fd5b886020828501011115610217575f80fd5b95989497505060200194505050565b60c0810183356001600160a01b038116808214610241575f80fd5b8352506020848101359083015260408085013590830152606080850135908301526080938401359382019390935260a001529056fea26469706673582212206da9bc84d514e1a78e2b4160f99f93aa58672040ece82f45ac2a878aeefdfbe164736f6c63430008190033" diff --git a/op-service/txintent/interop_call.go b/op-service/txintent/interop_call.go index 1fb70ccd82483..75fdcd1e796bf 100644 --- a/op-service/txintent/interop_call.go +++ b/op-service/txintent/interop_call.go @@ -4,7 +4,7 @@ import ( "fmt" "math/big" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" + "github.com/ethereum-optimism/optimism/op-core/predeploys" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -108,7 +108,7 @@ func (v *ExecTrigger) EncodeInput() ([]byte, error) { func (v *ExecTrigger) AccessList() (types.AccessList, error) { access := v.Msg.Access() accessList := types.AccessList{{ - Address: constants.CrossL2Inbox, + Address: predeploys.CrossL2InboxAddr, StorageKeys: suptypes.EncodeAccessList([]suptypes.Access{access}), }} return accessList, nil diff --git a/op-supervisor/README.md b/op-supervisor/README.md index f8fde2a1ca9e2..4080443dbecd6 100644 --- a/op-supervisor/README.md +++ b/op-supervisor/README.md @@ -350,4 +350,3 @@ sequenceDiagram - `op-e2e/interop`: Go interop system-tests, focused on offchain aspects of services to run end to end. - `op-e2e/actions/interop`: Go interop action-tests, focused on onchain aspects such as safety and state-transition. -- `kurtosis-devnet/interop.yaml`: Kurtosis configuration to run interoperable chains locally. diff --git a/ops/docker/op-stack-go/Dockerfile.dockerignore b/ops/docker/op-stack-go/Dockerfile.dockerignore index 6067e181296a6..000dd0ed53571 100644 --- a/ops/docker/op-stack-go/Dockerfile.dockerignore +++ b/ops/docker/op-stack-go/Dockerfile.dockerignore @@ -3,7 +3,6 @@ * !/cannon -!/devnet-sdk !/op-core !/op-batcher !/op-chain-ops diff --git a/rust/kona/tests/supervisor/l2reorg/init_exec_msg_test.go b/rust/kona/tests/supervisor/l2reorg/init_exec_msg_test.go index 1aa273c36bd00..61ccf76cc4249 100644 --- a/rust/kona/tests/supervisor/l2reorg/init_exec_msg_test.go +++ b/rust/kona/tests/supervisor/l2reorg/init_exec_msg_test.go @@ -5,9 +5,8 @@ import ( "testing" "time" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop" + "github.com/ethereum-optimism/optimism/op-core/predeploys" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-devstack/presets" @@ -15,6 +14,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/txintent" + "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" "github.com/ethereum-optimism/optimism/op-service/txplan" "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" "github.com/ethereum/go-ethereum/common" @@ -100,7 +100,7 @@ func TestReorgInitExecMsg(gt *testing.T) { execTx = txintent.NewIntent[*txintent.ExecTrigger, *txintent.InteropOutput](bob.Plan()) execTx.Content.DependOn(&initTx.Result) // single event in tx so index is 0. ExecuteIndexed returns a lambda to transform InteropOutput to a new ExecTrigger - execTx.Content.Fn(txintent.ExecuteIndexed(constants.CrossL2Inbox, &initTx.Result, 0)) + execTx.Content.Fn(txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &initTx.Result, 0)) var err error execReceipt, err = execTx.PlannedTx.Included.Eval(ctx) require.NoError(t, err) diff --git a/rust/kona/tests/supervisor/pre_interop/pre_test.go b/rust/kona/tests/supervisor/pre_interop/pre_test.go index 1ae079bf01d6d..869c62e7279e5 100644 --- a/rust/kona/tests/supervisor/pre_interop/pre_test.go +++ b/rust/kona/tests/supervisor/pre_interop/pre_test.go @@ -7,7 +7,6 @@ import ( "github.com/ethereum/go-ethereum/common" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-core/predeploys" @@ -78,7 +77,7 @@ func TestPreNoInbox(gt *testing.T) { // send executing message on chain B and confirm we got an error execTx := txintent.NewIntent[*txintent.ExecTrigger, *txintent.InteropOutput](bob.Plan()) execTx.Content.DependOn(&initMsg.Tx.Result) - execTx.Content.Fn(txintent.ExecuteIndexed(constants.CrossL2Inbox, &initMsg.Tx.Result, 0)) + execTx.Content.Fn(txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &initMsg.Tx.Result, 0)) execReceipt, err := execTx.PlannedTx.Included.Eval(sys.T.Ctx()) require.ErrorContains(err, "implementation not initialized", "error did not contain expected string") require.Nil(execReceipt) @@ -95,7 +94,7 @@ func TestPreNoInbox(gt *testing.T) { { ctx := sys.T.Ctx() - execTrigger, err := txintent.ExecuteIndexed(constants.CrossL2Inbox, &initMsg.Tx.Result, 0)(ctx) + execTrigger, err := txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &initMsg.Tx.Result, 0)(ctx) require.NoError(err) ed := stypes.ExecutingDescriptor{Timestamp: uint64(time.Now().Unix())}